Add Mutation and Randomization page

2026-04-18 06:05:32 -04:00
parent ff8dad3895
commit 366ea3eb2b

@@ -0,0 +1,102 @@
# Mutation and Randomization
DECNET's value as a deception network depends on the decoy fleet looking heterogeneous at deploy time and shifting over its lifetime. This page documents the two mechanisms that deliver that: randomization at build, and mutation at runtime.
See also: [CLI reference](CLI-Reference), [Archetypes](Archetypes), [Distros](Distro-Profiles).
## Randomization at deploy time
### `--randomize-services`
When `decnet deploy` is invoked with `--randomize-services`, each decky receives a randomly drawn service set instead of the fixed list passed via `--services` or the set implied by `--archetype`.
The selection logic lives in `build_deckies()` (`decnet/fleet.py`). For each decky:
1. A count `k` is drawn uniformly from `[1, min(3, len(pool))]`.
2. `k` service names are sampled without replacement from the pool (`all_service_names()`, or the archetype service list if an archetype is set).
3. The chosen set is compared against a `used_combos: set[frozenset]` that tracks combinations already assigned in this deploy. If it collides with an existing combination, the draw is retried.
4. After 20 retries, the last draw is accepted even if duplicated. With small pools and many deckies, exact uniqueness is not always possible — the retry cap prevents an infinite loop.
### Random hostnames
Hostnames are generated by `random_hostname(distro_slug)` in `decnet/distros.py`. The style depends on the distro profile's `hostname_style` field:
| Style | Example | Distros |
|-----------|-----------------------|---------------------------------|
| `generic` | `SRV-PROD-42` | Debian, Ubuntu |
| `rhel` | `web37.localdomain` | Rocky, CentOS, Fedora |
| `minimal` | `alpha-18` | Alpine |
| `rolling` | `nova-backup` | Kali, Arch |
Word pool and numeric range are defined in `_NAME_WORDS` and the `random.randint(10, 99)` call in the same file.
### Random distros
`random_distro()` picks a uniform-random entry from the `DISTROS` dict (`decnet/distros.py`). Each entry is a `DistroProfile` with a slug, a Docker image, a display name, a hostname style, and a build base image used for service Dockerfiles (which assume `apt-get`, so non-Debian distros fall back to `debian:bookworm-slim` for builds).
The current set: `debian`, `ubuntu22`, `ubuntu20`, `rocky9`, `centos7`, `alpine`, `fedora`, `kali`, `arch`.
### MAC addresses
MAC addresses are not assigned by DECNET. The MACVLAN driver auto-generates a MAC for each container interface at container start. There is no knob to pin or rotate them from the deploy config; if you need deterministic MACs, attach them out-of-band at the Docker network layer.
## Mutation at runtime
Randomization only fires at build time. To keep the fleet moving, DECNET supports per-decky service rotation.
### Storage
Every `DeckyConfig` (`decnet/config.py`) carries two mutation-related fields:
- `mutate_interval: int | None` — minutes between rotations for this decky. `None` disables automatic rotation for that decky.
- `last_mutated: float` — Unix timestamp of the most recent successful mutation.
The top-level `DecnetConfig` also holds a fleet-wide `mutate_interval`, which defaults to `DEFAULT_MUTATE_INTERVAL = 30` (minutes) from `decnet/config.py`. Per-decky values override the fleet default.
### Engine
`decnet/mutator/engine.py` exposes three async entry points, all operating against a `BaseRepository`:
- `mutate_decky(decky_name, repo)` — Intra-archetype shuffle for one decky. Rebuilds the service list by sampling 1-3 services from the decky's archetype pool (or the full registry if no archetype is set), retrying up to 20 times to avoid picking the exact same set. Updates `last_mutated`, persists state, rewrites the compose file, and runs `docker compose up -d --remove-orphans`.
- `mutate_all(repo, force=False)` — Iterates all deckies. For each, computes `elapsed = now - last_mutated` and calls `mutate_decky` when `elapsed >= interval * 60`. `force=True` bypasses the schedule.
- `run_watch_loop(repo, poll_interval_secs=10)` — Infinite loop that calls `mutate_all` every `poll_interval_secs`. Invoked by `decnet mutate --watch`.
### Trigger from the CLI
```bash
# Deploy a fleet that rotates every 15 minutes
decnet deploy --mode unihost --deckies 5 --interface eth0 \
--randomize-services --mutate-interval 15
# Mutate a single decky now (forces immediately, ignores schedule)
decnet mutate --decky decky-03
# Mutate all deckies now
decnet mutate --all
# Run the watcher in the foreground
decnet mutate --watch
# Run the watcher as a detached daemon
decnet mutate --watch --daemon
```
`decnet deploy` also starts a background watcher automatically; the standalone `decnet mutate --watch` is for operators who want to run the loop themselves.
## Operational trade-offs
Mutation is a blunt instrument. The interval you pick is a trade between deception fidelity and observability:
- **Short intervals (≤ 5 min)**: The fleet looks lively and fingerprint scans will never converge, but every rotation churns containers, rewrites compose files, and wipes short-lived attacker state — half-finished brute-force sessions, partially uploaded payloads, active TCP connections. You will lose IOCs that would otherwise have been captured.
- **Default (30 min)**: Reasonable balance. Most scan-and-go attackers see a consistent snapshot; long-dwell attackers see the fleet shift under them, which itself is a useful signal.
- **Long intervals (hours) or `None`**: The fleet looks static. An attacker who fingerprints twice gets identical results, which is not how real networks behave under patching and reconfiguration.
For research deployments where you want a specific attacker session recorded end-to-end, disable mutation on the decky under observation (`mutate_interval=None` on that decky only) while leaving the rest of the fleet rotating.
## Sources
- `decnet/distros.py``DISTROS`, `random_hostname`, `random_distro`
- `decnet/fleet.py``build_deckies`
- `decnet/mutator/engine.py``mutate_decky`, `mutate_all`, `run_watch_loop`
- `decnet/config.py``DEFAULT_MUTATE_INTERVAL`, `DeckyConfig`, `DecnetConfig`
- `decnet/cli.py``deploy`, `mutate` commands