# =============================================================================
#  meridian-bank.yaml  —  a CUSTOMER-MIRRORED range, built as a teaching example
# =============================================================================
#
#  Scenario: a fictional mid-size regional bank, "Meridian Trust."
#  This file shows how you take the stock ad-lateral-movement range and bend it
#  to look like a real customer's environment — their VLANs, their hostnames,
#  their firewall posture — by editing TEXT. No programming.
#
#  Every field below is real (validated by the downrange/v1 schema). Lines
#  marked  # >> DEMO  are the ones you'd edit live in front of a design partner.
#  Lines marked  # ROADMAP  describe capability that is designed but not yet
#  live — say so honestly; don't imply it boots today.
#
#  YAML rules you need (that's all): key: value, indent with SPACES (never
#  tabs), "- " starts a list item, "{ }" is a compact inline block. Done.
# =============================================================================

apiVersion: downrange/v1        # never change — tells the parser the format
kind: Scenario                  # never change

# ---- metadata: the label on the box -----------------------------------------
metadata:
  name: meridian-bank                       # kebab-case id (a-z, 0-9, dashes)
  title: "Meridian Trust — AD lateral movement"   # >> DEMO: customer's name
  difficulty: intermediate                  # beginner | intermediate | advanced
  est_duration_minutes: 45
  mitre_tactics:                            # the ATT&CK tactics this exercises
    - initial-access
    - credential-access
    - lateral-movement

# ---- segments: the customer's network zones (their VLANs) --------------------
#  THIS is the first thing you change for a customer. Ask: "what are your
#  subnets?" and type them here. Below is Meridian's addressing, not ours.
segments:
  endpoints:                                # >> DEMO: their workstation VLAN
    cidr: 10.40.10.0/24
    gateway: 10.40.10.1
  datacenter:                               # >> DEMO: their server VLAN
    cidr: 10.40.20.0/24
    gateway: 10.40.20.1
  security:                                 # the SOC / tooling VLAN
    cidr: 10.40.99.0/24
    gateway: 10.40.99.1

# ---- nodes: the machines that live in those zones ----------------------------
#  Each node needs only `kind` and `image`. Everything else is optional.
#  `image` must be something we have a working golden/container for (see the
#  Supported Stack list) — typing a name we don't build won't conjure it.
nodes:

  # -- the domain controller: the crown jewel the attacker is after ----------
  mtdc01:                                    # >> DEMO: their real DC hostname
    kind: vm
    image: winsrv-adds                       # the only Windows DC golden today
    segment: datacenter
    ram: 4G
    role: domain-controller
    crown_jewel: true                        # marks the prize for scoring
    telemetry: [windows-security]            # which logs feed the SIEM

  # -- a member file server --------------------------------------------------
  mtfs01:                                    # >> DEMO: their file server name
    kind: vm
    image: winsrv-fileserver
    segment: datacenter
    ram: 4G
    role: fileserver

  # -- an analyst workstation (the adversary's foothold) ---------------------
  #  ROADMAP: win11 endpoints are delivered via the Azure track (ADR 0002) and
  #  do NOT boot on the local cluster yet. Endpoint-side detections (process
  #  exec, LSASS access) light up when that track lands. Keep the node in the
  #  file so the topology is honest about what the network contains.
  teller-ws:                                 # >> DEMO: their workstation name
    kind: vm
    image: win11-domainjoined                # ROADMAP: Azure-delivered
    segment: endpoints
    role: foothold

  # -- the router/firewall: one node, owns the segment policy ----------------
  rtr:
    kind: container
    image: frrouting/frr:latest
    # rtr bridges all segments; it has no single `segment`.

  fw:
    kind: container
    image: downrange/nftables-fw:latest
    # the fw node OWNS the firewall policy below but renders no pod of its own —
    # the rules are enforced inside the rtr's network namespace.
    policy:                                  # <<< THE FIREWALL — see section 2
      # Read like an ACL: from-zone -> to-zone -> allowed ports. Default-deny,
      # first match wins. This is where you mirror a customer's firewall.
      - allow: { from: endpoints,  to: datacenter, ports: [smb/445, rdp/3389, any:kerberos/88, any:ldap/389, any:dns/53] }
      - allow: { from: endpoints,  to: security,   ports: [wazuh-events/1514, wazuh-enroll/1515] }
      - deny:  { from: endpoints,  to: security }              # block the rest to the SOC
      - deny:  { from: datacenter, to: endpoints }             # servers don't dial workstations

  # -- the blue-team SIEM console --------------------------------------------
  siem:
    kind: container
    image: downrange/wazuh-aio:latest
    segment: security
    ram: 4G
    role: blue-team-console

# ---- links: which node interfaces connect to which ---------------------------
#  Format is  node:iface . For most ranges the defaults are fine; you rarely
#  hand-edit this once the segments and nodes are right.
links:
  - [rtr:eth1, endpoints:gw]
  - [rtr:eth2, datacenter:gw]
  - [rtr:eth3, security:gw]

# ---- adversary: the attack to run --------------------------------------------
#  engine: caldera drives a real attack. A ttp_chain step only RUNS if a Caldera
#  ability exists for its technique — otherwise the launcher refuses to start
#  (by design: "a half-armed adversary is worse than a loud failure").
#
#  RUNNABLE TODAY = the four DC-side techniques: T1078, T1550, T1558.003,
#  T1003.006. The endpoint steps (exec/creddump on the workstation) are ROADMAP
#  — they need the Azure endpoint track. This chain is written to run live now.
adversary:
  engine: caldera
  pace: medium                               # slow | medium | fast
  entry: { from: attacker, to: teller-ws, technique: T1566 }   # how the adversary gets in
  objective: domain-admin@mtdc01             # the win condition
  ttp_chain:
    - { step: foothold, mitre: T1078,     on: mtdc01 }         # validate stolen creds
    - { step: pth,      mitre: T1550,     on: mtdc01 }         # pass-the-hash to DA
    - { step: kerberoast, mitre: T1558.003, on: mtdc01 }       # roast the SQL service account
    - { step: dcsync,   mitre: T1003.006, on: mtdc01 }         # replicate krbtgt secrets
    # ROADMAP (needs Azure endpoint track — HAR-34 / HAR-60):
    # - { step: exec,     mitre: T1059, on: teller-ws }
    # - { step: creddump, mitre: T1003, on: teller-ws }

# ---- blue_team: what the defenders are running -------------------------------
#  console can be wazuh today; byo-splunk / byo-sentinel are roadmap values
#  already recognized by the schema — that's the "bring your own SIEM" seam.
blue_team:
  console: wazuh                             # ROADMAP options: byo-splunk, byo-sentinel
  endpoint: [wazuh-agent, sysmon]
  response_actions: [isolate-host, cut-segment]

# ---- scoring: what counts as a catch, and what it's worth --------------------
#  Each objective ties a detection rule to points + an SLA. The `rule` must
#  match a rule in the ruleset (format  source:number ). These four are the
#  DC-side detections that fire and score live today.
scoring:
  ruleset: meridian-detections
  objectives:
    - { id: pth,        mitre: T1550,     signal: ntlm-auth,  rule: "wazuh:100140", points: 20, sla_minutes: 10 }
    - { id: kerberoast, mitre: T1558.003, signal: kerb-tgs,   rule: "wazuh:100160", points: 20, sla_minutes: 10 }
    - { id: dcsync,     mitre: T1003.006, signal: dir-replication, rule: "wazuh:100150", points: 25, sla_minutes: 5 }
  containment:
    - { id: contain-before-dc, description: "Isolate the foothold before DCSync completes", type: boolean, points: 75 }
  metrics: [mttd, mttr, contained_before_dc, mitre_coverage]

# ---- report: the after-action output -----------------------------------------
report:
  template: after-action-v1
  surface: [score, timeline, mitre-map]

# ---- runtime: cost + lifecycle guardrails ------------------------------------
runtime:
  idle_suspend_minutes: 30                   # suspend an idle range (stop metering)
  hard_ttl_minutes: 180                      # hard kill at 3h so nothing runs forever
  metered: true                              # bill by the range-hour
