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:
| State | What it means | How you reach it |
|---|---|---|
| Un-adopted | Fresh node, temporary self-signed CA. Serves a maintenance mode on :6443 that exposes only the adoption endpoint, and answers discovery. | sihactl adopt --secret … |
| Adopted | Your 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:
endpointis 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 infleet.yaml.- Interfaces read from their shape:
pci:is a physical DPDK port,bond:/bridge:/vxlan:declare those kinds, VLANs nest under their parent. Nonetworks:block, no prefix-length lookup — platform-agnostic. - Add more nodes under
appliances:, share policy with groups and patches ($patch: delete,@./patch.yamlfiles) — see Declarative fleet for the full layering model. configs/is regenerable: re-rungen configany time you editfleet.yaml. Preview what a node derives withsihactl gen config -f fleet.yaml --show-derived fw-1.
If you drive SiHA through the bundled
taskworkflow,task up ENV=<env>renders the equivalent for the bundled test envs (intogen/<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.10you declared asfw-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:
- The CLI reads your CA from
--secretand pushes it to the node’s adoption endpoint. - The node validates the CA, installs it, re-issues its server certificate under it, marks itself adopted, and restarts into full mutual-TLS.
- 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:6443instead — 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.