Getting started

Getting started

Bring up your first SiHA node from a blank image to a running, configured firewall — the zero-touch bootstrap flow, end to end.

This guide takes you from a blank, unconfigured node to a running, configured SiHA firewall — the whole bootstrap. It assumes nothing: no prior SSH, no cloud-init, no pre-shared secret on the box. You mint a fleet secret, describe your fleet and render its config, then image each node, find it, and adopt it — all over the API.

The idea — a node adopts itself

A SiHA image ships with no identity. The very first time a node boots it is un-adopted: it mints a temporary self-signed CA, comes up reachable, and waits for an operator to claim it. Adoption is the moment you hand the node your real Certificate Authority — from then on it trusts (and is reachable by) only clients holding a certificate signed by that CA.

Two states, one gate:

StateWhat it meansHow you reach it
Un-adoptedFresh node, temporary self-signed CA. Serves a maintenance mode on :6443 that exposes only the adoption endpoint, and answers discovery.sihactl adopt --secret …
AdoptedYour CA is installed. Full mutual-TLS; discovery goes silent.sihactl config apply --sihaconfig … (normal, mTLS)

The switch is a single marker on the node’s writable partition. Everything else — which TLS mode apid serves, whether the node answers discovery, whether it accepts an adoption — keys off that one flag. There is no CA baked into the image and none injected by cloud-init: adoption over the API is the one and only way a node gets its identity.

Trust model — TOFU. An un-adopted node accepts the first CA pushed to it on its local network, then refuses any other (Trust On First Use). The L2 segment is the trust boundary: adopt your nodes on a network you control.

1 · Mint your fleet secret (once per fleet)

One CA anchors your whole fleet, held operator-side in a secret file. Mint it once:

sihactl gen secret prod -o siha-secret.yaml

The secret holds the CA certificate and its private key — used only to adopt nodes (to push the CA onto them). Guard it; SOPS-encrypt it at rest:

sops -e -i siha-secret.yaml

You keep it encrypted from here on: sihactl gen config and config apply --secret read the ciphertext directly and decrypt it in-process with the age identity from SOPS_AGE_KEY_FILE (or SOPS_AGE_KEY) — the same env vars sops itself honors. Export it once and every later command works against the encrypted file, no manual decrypt step:

export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt

Your day-2 admin identity (a client cert, no CA key) is derived from this secret in the next step, when you render your fleet. One CA, one admin identity, reaches every node you adopt — each node self-mints its own server certificate under your CA at adoption, so there is no per-node PKI.

2 · Describe your fleet and render it

You don’t hand-write each node’s config. Describe your whole fleet in one fleet.yaml and render it — that produces both every node’s config bundle and your operator sihaconfig (the day-2 identity). Here is a minimal, single-node fleet.yaml:

version: v1alpha1
fleetName: prod

appliances:
  - name: fw-1
    endpoint: "192.168.1.10:6443"

    network:
      interfaces:
        # The shape names the kind: `pci:` = a PMD (DPDK) port.
        - name: wan
          pci: "0000:05:00.0"
          addresses: ["203.0.113.2/24"]
        - name: lan
          pci: "0000:06:00.0"
          addresses: ["10.0.0.1/24"]

    firewall:
      acls:
        - name: wan-in
          family: IPv4
          defaultAction: DROP
          rules:
            - seq: 10
              action: ACCEPT_STATEFUL
              protocol: 6            # TCP
              destinationPortFirst: 443
              description: "HTTPS in"
          bindTo:                    # bindings live on the ACL
            - interface: wan
              direction: INPUT

    nat:
      interfaces:
        - name: lan-inside
          interface: lan
          role: inside
        - name: wan-outside
          interface: wan
          role: outside

Render it:

sihactl gen config -f fleet.yaml --secret siha-secret.yaml

This writes everything under configs/ — auto-gitignored, because it holds keys:

configs/
  sihaconfig      # your day-2 admin identity (context "prod", every endpoint)
  fw-1.yaml       # fw-1's appliance config — ONE document, ready to apply

A few things worth knowing:

  • endpoint is the management address where the node’s API answers — it comes from your platform (DHCP or a static seed), not from SiHA. The management NIC is platform-owned and not declared in fleet.yaml.
  • Interfaces read from their shape: pci: is a physical DPDK port, bond:/bridge:/vxlan: declare those kinds, VLANs nest under their parent. No networks: block, no prefix-length lookup — platform-agnostic.
  • Add more nodes under appliances:, share policy with groups and patches ($patch: delete, @./patch.yaml files) — see Declarative fleet for the full layering model.
  • configs/ is regenerable: re-run gen config any time you edit fleet.yaml. Preview what a node derives with sihactl gen config -f fleet.yaml --show-derived fw-1.

If you drive SiHA through the bundled task workflow, task up ENV=<env> renders the equivalent for the bundled test envs (into gen/<env>/) for you.

3 · Boot a node

Image the node and power it on. With no configuration it comes up un-adopted and makes itself reachable:

  • If it gets a static or DHCP mgmt address, it uses that (e.g. the 192.168.1.10 you declared as fw-1’s endpoint).
  • If it finds no address at all (no seed, no DHCP), it falls back to a break-glass IP — a link-local address plus 192.168.1.1/24 — and emits a gratuitous ARP so your switch learns it. You can always reach a fresh node.

Nothing else is needed on the node. No SSH, no console login, no secrets.

4 · Find the node

From a machine on the same L2 segment:

sihactl discover
HOSTNAME     MGMT               UNADOPTED  CA-FINGERPRINT
fw-1         192.168.1.10/24    true       3E:72:CD:90:56:1D:BF:0F:…

discover broadcasts a small solicit and lists every un-adopted node that answers, with its address and the fingerprint of its temporary CA. It needs no root and no special privileges — just a broadcast-capable network. Adopted nodes don’t answer, so an empty list means “everything here is already claimed.”

5 · Adopt and configure

Adopt

Claim the node with your fleet secret:

sihactl adopt --secret siha-secret.yaml --addr 192.168.1.10:6443

Under the hood, in order:

  1. The CLI reads your CA from --secret and pushes it to the node’s adoption endpoint.
  2. The node validates the CA, installs it, re-issues its server certificate under it, marks itself adopted, and restarts into full mutual-TLS.
  3. The CLI waits for mTLS to come up before returning.

adopt is idempotent: if the node is already adopted it exits 0 immediately. Run it again at any time — it is safe.

Adopting via the break-glass address? Pass --addr 192.168.1.1:6443 instead — everything else is identical.

Check state

Once adopt returns, confirm the node is up:

sihactl status --addr 192.168.1.10:6443

status gives a one-screen overview — adopted / maintenance / unreachable — and answers at any stage of the adoption lifecycle (mTLS probe first, then the open maintenance channel).

Apply configuration

With the node adopted, push its config bundle over the now-trusted mTLS channel:

sihactl --sihaconfig configs/sihaconfig \
  config apply -f configs/fw-1.yaml

The document is declarative: what you remove from fleet.yaml is pruned from the node on the next apply.

ZTP one-shot (alternative)

If you prefer a single command that does adopt + apply in one step, use:

sihactl config apply --insecure \
  --secret siha-secret.yaml \
  --sihaconfig configs/sihaconfig \
  --addr 192.168.1.10:6443 \
  -f configs/fw-1.yaml

--insecure signals “I haven’t established trust yet”. After pushing the CA it waits for the node to restart, then applies the config over mTLS in one go. The command is idempotent: on an already-adopted node it skips adoption and only re-applies the config.

6 · Verify

sihactl status --addr 192.168.1.10:6443

This one-screen check probes with your mTLS identity first, then falls back to the open maintenance channel — it answers at any stage of the adoption lifecycle. For a full service health view:

sihactl --sihaconfig configs/sihaconfig --addr 192.168.1.10:6443 health
status: STATUS_HEALTHY
  vpp        STATUS_HEALTHY  restarts=1     ← the adoption restart
  apid       STATUS_HEALTHY  restarts=0
  pilotd     STATUS_HEALTHY  restarts=0
  probed     STATUS_HEALTHY  restarts=0
  metricsd   STATUS_HEALTHY  restarts=0

A restarts=1 on VPP is expected — that’s the single restart adoption performs. Run sihactl discover again and the node is gone from the list: it’s adopted.

Day two — configuring an adopted node

Once a node is adopted you never use --insecure (or --secret) again. The loop is: edit fleet.yaml → re-render → apply, over normal mutual-TLS with your sihaconfig identity:

sihactl gen config -f fleet.yaml --secret siha-secret.yaml   # re-render
sihactl --sihaconfig configs/sihaconfig config apply -f configs/fw-1.yaml

Same command shape, minus --insecure and --secret — because now both sides authenticate. --addr is optional here: with one context the sihaconfig supplies the endpoint. From here on everything is declarative: you apply resources (interfaces, ACLs, NAT, routes, load-balancer VIPs…) and the control plane converges the dataplane to match. See Resources for the object model and CLI · sihactl for the full command surface.

Recovering a node

If a node is ever unreachable (a bad config, a lost network), the break-glass IP (192.168.1.1/24 + link-local) is always available on boot when no other address resolves — reach it and re-apply. The break-glass address is configurable (sihactl mgmt set) and takes effect on the next boot.