# Tutorial: a pool of disposable backend VMs (key auth, capacity, reaping)

**Audience:** Operators comfortable with a single Live backend (see
[first Live session](live-first-execution.md)) who want a small **fleet** of
disposable VMs with key-based auth, host-key pinning, capacity limits, and
automatic lease recycling.

**Time:** about 30-45 minutes
**Outcome:** You run Live against multiple backend VMs defined in a JSON config,
watch sessions round-robin across them, hit the capacity cap, and see idle/
time-boxed leases get reaped.

!!! danger "Every backend is burnable"
    Each VM in the pool runs real attacker code. Use throwaway VMs on an
    isolated network with sinkhole egress. Destroy them after use.

## What you will learn

1. Defining multiple backends in a JSON file (keeps credentials off the CLI)
2. Using **key authentication** and **host-key pinning**
3. Setting warm-pool and hard-cap capacity, and what happens at the cap
4. Watching time-box / idle reaping recycle leases

## Prerequisites

- Two (or more) disposable Linux VMs with SSH, reachable from the sensor host.
  For the lab, two containers published on `127.0.0.1:22022` and
  `127.0.0.1:22023` with a throwaway `guest` account work fine.
- The `sensor` binary.

## Step 1: Prepare backend credentials

You can mix password and key auth per target. To use a key, generate a
throwaway keypair and install the public key on the guest(s):

```bash
ssh-keygen -t ed25519 -N '' -f /tmp/chn-live-guest
# copy /tmp/chn-live-guest.pub into each guest's authorized_keys for 'guest'
```

Optionally pin each guest's host key so a swapped/MITM backend is rejected:

```bash
ssh-keygen -lf <(ssh-keyscan -p 22022 -t ed25519 127.0.0.1 2>/dev/null)
# -> 256 SHA256:2f0d...c1 ...   (copy the SHA256:... value)
```

## Step 2: Write the backend JSON

Create `live-backend.json`. Targets from the file are merged and leases
round-robin across all of them.

```json
{
  "targets": [
    {
      "address": "127.0.0.1:22022",
      "username": "guest",
      "private_key_path": "/tmp/chn-live-guest",
      "host_key_fp": "SHA256:2f0d...c1"
    },
    {
      "address": "127.0.0.1:22023",
      "username": "guest",
      "password": "guestpw"
    }
  ]
}
```

Notes:

- Provide **either** `password` or `private_key_path` per target.
- `host_key_fp` is optional; empty means trust-on-first-use (the sensor logs a
  warning). Pin it on anything but a fully trusted private network.
- Keep this file readable only by the sensor user (`chmod 600`).

## Step 3: Start Live with the pool

```bash
export CYBERHALLUCINET_LIVE_ACK=I_UNDERSTAND_LIVE_INTERACTION

sensor \
  --profile=production \
  --interaction=live \
  --listen-status=127.0.0.1:8080 \
  --listen-ssh=127.0.0.1:2222 \
  --live-backend-config=./live-backend.json \
  --live-pool-warm=2 \
  --live-pool-max=3 \
  --live-session-minutes=10 \
  --live-idle-minutes=2
```

Startup logs the target count and capacity:

```
sensor: LIVE proxy enabled: 2 disposable VM target(s), warm=2 max=3 ...
```

- `--live-pool-warm=2` pre-allocates two leases so the first attackers connect
  without a cold start.
- `--live-pool-max=3` is the hard cap on concurrent guest leases.

## Step 4: Round-robin across backends

Open a couple of attacker sessions and print the backend hostname in each:

```bash
# terminal 1
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null app@127.0.0.1 \
  'hostname; echo session-A'

# terminal 2
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null app@127.0.0.1 \
  'hostname; echo session-B'
```

Successive leases are dialed against alternating targets (round-robin). If your
two backends have different hostnames, you will see them differ across sessions.

## Step 5: Hit the capacity cap

Open interactive sessions and keep them open until you exceed `--live-pool-max`.
The session beyond the cap cannot lease a guest, so it **fails closed to the
Emulated shell** rather than over-committing the backend fleet. In telemetry you
will see:

```
kind=live_downgrade target=pool_exhausted
```

This protects your disposable fleet from unbounded fan-out under a flood while
still giving every attacker a plausible shell.

## Step 6: Watch leases get reaped

You set an aggressive `--live-idle-minutes=2` and `--live-session-minutes=10`.

- Leave a session idle for just over two minutes; a background reaper (runs
  every 30s) recycles the idle lease. The sensor logs:

```
sensor: live pool reaped N guest lease(s) (time-box/idle)
```

- A session open past ten minutes is time-boxed and reaped the same way.

After a lease is reaped, that capacity returns to the pool for new attackers.

## Step 7: Tear down

```bash
# Ctrl-C the sensor, then destroy every backend VM in the pool:
docker rm -f chn-live-guest-1 chn-live-guest-2 2>/dev/null || true
rm -f /tmp/chn-live-guest /tmp/chn-live-guest.pub live-backend.json
```

## Checkpoint: did it work?

- [ ] Startup reported 2 targets and your warm/max capacity
- [ ] Sessions round-robined across backends
- [ ] Exceeding the cap produced `live_downgrade target=pool_exhausted`
- [ ] Idle/time-boxed leases were reaped and logged
- [ ] You destroyed all backend VMs

## What to read next

- [Reference: backend JSON schema & capacity flags](../reference/live-proxy.md#backend-json-schema-live-backend-config)
- [Explanation: capacity, lifecycle, and the kill-switch](../architecture/live-proxy.md#capacity-lifecycle-and-the-kill-switch)
- [Network isolation](../security/network-isolation.md): sinkhole egress for
  the backend fleet
