Architecture
Taranac is not a single daemon — it is a small set of containers that each own one job (the API, the protocol engines, the database, the edge) and share a single source of truth in PostgreSQL. You manage everything from one admin UI; the protocol containers read their working configuration from a shared volume and the database, never from hand-edited files. This page explains what the moving parts are, how they relate, and what actually happens when a switch, router, or laptop asks Taranac to authenticate.
The whole stack runs under Docker Compose. In the default single-host deployment there is no separate “controller” and “node” tier — the same compose file brings up the API, the AAA engines, NAC, the captive portal, the database, and the public edge proxy together. Under the optional high-availability topology this changes: the database becomes a Patroni-managed primary/replica cluster across two or more hosts, layered on by an HA overlay compose file — see the HA section below.
The dashboard surfaces live activity from every container — AAA and NAC authentications, sessions, and certificate expiry — in one place.
config rendered to a shared volume · in-memory caches refresh ~30s
The containers
Section titled “The containers”Each box below is a separate container. They communicate over an internal Docker network; only the edge proxy and the protocol listeners are exposed to the outside.
| Container | What it does | Talks to |
|---|---|---|
| edge | Public reverse proxy (nginx). Terminates the TLS your browser and phones see, and proxies inward to the UI, the API, and optionally the MFA server. | frontend, api, taranac-mfa |
| frontend | The admin UI (React, served by nginx). Everything an operator configures happens here. | api |
| api | The backend (FastAPI). The brain of the system: stores all configuration, renders engine configs, owns the identity store, exposes the REST API. | postgres, all engines (via shared volumes) |
| tacacs | TACACS+ device administration. Runs tac_plus-ng with a Python MAVIS handler for authentication, MFA, and enable. Listens on port 49. | postgres, shared config volume |
| radius | RADIUS device administration (AAA). FreeRADIUS 3.2.8 with a Python handler that does the full policy evaluation. Virtual server aaa. | postgres, shared config volume |
| nac | 802.1X / port access control. A separate FreeRADIUS 3.2.8 container with its own handler, EAP stack, and policy engine. Virtual server nac. Also issues Change-of-Authorization (CoA). | postgres, shared config volume |
| captive-portal | Guest, BYOD, self-registration, and AUP flows (nginx + a small Preact app). Can run embedded here, or stand alone in a DMZ. | api (over a portal token) |
| taranac-mfa | Push MFA backend (FastAPI). Delivers push challenges via FCM with TOTP/OTP fallback. | postgres, api |
| postgres | PostgreSQL 16 — the single shared store for all configuration, identities, logs, and runtime session state. | — (everything talks to it) |
Why TACACS+ and RADIUS behave differently
Section titled “Why TACACS+ and RADIUS behave differently”Both protocols cover “who can manage the network gear”, but they do policy differently, and this shapes what you can configure:
| Aspect | TACACS+ | RADIUS (AAA) |
|---|---|---|
| Policy evaluation | Done by tac_plus-ng from generated config | Done entirely by the Python handler |
| What the auth handler decides | Authentication + WHO matching only | Authentication + full policy evaluation |
| Enable password | Yes — with an inheritance chain | No concept of enable |
| Per-command authorization | Yes — Command Sets | No concept of it |
| Console / port conditions | Yes | Ignored (TACACS+-only) |
You do not need to know this to use the UI, but it explains why Command Sets and enable settings appear only under TACACS+, and why a RADIUS policy is purely “match conditions → assign a profile.”
The shared config and identity store
Section titled “The shared config and identity store”Two mechanisms tie the containers into one product.
1. PostgreSQL is the single source of truth. Users, groups, devices, policies, profiles, certificates, logs, and live session state all live in one database. The protocol engines do not own any of this — they read it. Each engine keeps small in-memory caches (users, devices, policy rules, profiles) that refresh from the database roughly every 30 seconds, so a change you make in the UI takes effect within seconds without a restart.
2. Generated config on a shared volume. Some settings — chiefly the list of network devices and their shared secrets — can’t be served from a cache at request time; the protocol daemons need them in their own config files. When you save a device or change a setting, the API renders the engine configuration and writes it to a Docker volume that the relevant container mounts. A watcher inside the container then validates and reloads:
Admin UI → API renders config → writes to shared volume → watcher validates → reload signal → tac_plus-ng / FreeRADIUS picks it upThe reload is deliberately careful. For FreeRADIUS, if only modules or virtual servers changed, the engine is sent a hot HUP with no downtime; if the device list (clients.conf) changed, the container does a full restart, because FreeRADIUS 3.x cannot add or remove clients from its in-memory table on a HUP. You never edit these files by hand — the UI owns them.
Internal TLS
Section titled “Internal TLS”Server-to-server traffic (API ↔ engines ↔ MFA) runs over HTTPS using a self-signed internal CA that Taranac generates automatically on first start. The CA and per-service certificates are distributed through a shared taranac_certs volume. This is entirely separate from the public certificate the operator supplies for the edge proxy — the internal CA is never exposed to browsers.
How an authentication flows
Section titled “How an authentication flows”The request path depends on which engine the network device talks to.
Device login over TACACS+
Section titled “Device login over TACACS+”Network device → tacacs (tac_plus-ng) → MAVIS handler: 1. Look up the user (local or LDAP) and verify the password 2. Apply MFA if the policy requires it 3. Match WHO / WHEN policy conditions → return matching rule profiles→ tac_plus-ng applies first-match-wins, WHERE/SOURCE conditions, and per-command rules→ device gets shell, privilege level, and allowed commandsDevice login over RADIUS (AAA)
Section titled “Device login over RADIUS (AAA)”Network device → radius (FreeRADIUS, VS aaa) → Python handler: 1. Identify the device by NAS IP; look up the user 2. Evaluate policy (WHO + WHERE + SOURCE + WHEN), first-match-wins 3. Reject, authenticate-only, or render an authorization profile 4. Apply MFA challenge if required→ Access-Accept with the profile's reply attributesEndpoint joining a port over 802.1X / MAB (NAC)
Section titled “Endpoint joining a port over 802.1X / MAB (NAC)”Endpoint → switch port → nac (FreeRADIUS, VS nac) → NAC handler: • 802.1X (PEAP / EAP-TLS / EAP-TTLS): negotiate EAP, verify the user or certificate, enrich the endpoint • MAB (no supplicant): use the MAC as identity, look up the endpoint, auto-discover and classify if unknown → evaluate NAC policy (HOW / WHO / WHAT / WHERE / WHEN), first-match-wins → render the authorization profile into vendor-correct RADIUS attributes→ Access-Accept with VLAN / dACL / redirect, or Access-Reject→ accounting tracks the live session; CoA can re-authorize it laterThe same authorization profile — say “Printers, VLAN 300, restricted ACL” — works across every switch vendor. The NAC engine consults a vendor dictionary to translate that intent into the exact RADIUS attributes a Cisco, Arista, or Aruba switch expects.
NAC policy rules evaluate first-match-wins; each rule’s conditions map to an authorization profile or a reject.
How the database ties it together
Section titled “How the database ties it together”Almost every object an engine evaluates is a shared record you manage once:
| Object | Configured under | Used by |
|---|---|---|
| Users, Groups | Identity | TACACS+, RADIUS, NAC (PEAP/TTLS) |
| Network Devices, Device Groups | Network | All three engines (as the NAS/authenticator, with shared secrets) |
| LDAP directories | Identity | All engines (external auth, EAP-TTLS bind, NAC computer sync) |
| Time Ranges | AAA | WHEN condition in every policy |
| TACACS+ / RADIUS / NAC profiles | AAA, NAC | The matching engine’s authorization result |
| Endpoints, Endpoint Groups | NAC | NAC policy WHAT condition |
| Internal PKI, Trusted CAs | NAC | EAP-TLS certificate validation |
Because the identity store and the device list are shared, the same user or the same switch is defined exactly once and is consistent across TACACS+, RADIUS, and NAC.
Network devices are defined once and reused by every protocol engine, with shared-secret inheritance from device groups.
When to use which engine
Section titled “When to use which engine”| You want to control… | Use | Why |
|---|---|---|
| CLI / admin access to switches and routers, with per-command control and enable | TACACS+ | Only TACACS+ offers command authorization, enable, and console/port conditions |
| CLI / admin access where the gear only speaks RADIUS | RADIUS (AAA) | RADIUS device administration with profile-based authorization |
| Which devices may connect to a switch port (laptops, printers, IoT, phones) | NAC (802.1X / MAB) | Port-level access with VLAN/ACL assignment and live session control |
| Onboarding guests, BYOD, or self-registration | Captive portal + NAC | The portal registers the endpoint; NAC re-authorizes it via CoA |
High availability
Section titled “High availability”A single-host Compose deployment is the default topology. For deployments where authentication must survive a host failure, Taranac ships a turnkey high-availability mode as a licensed Pro feature. Because all durable state lives in PostgreSQL, HA is built around replicating the database: the stack runs across two or more hosts with a Patroni-managed primary/replica PostgreSQL cluster (etcd as the coordination store, plus a lightweight witness for two-node setups), and the API/engine tier follows the elected leader for writes while reads can be served from replicas. An HA overlay compose file adds these pieces on top of the standard stack; converting a running single-host deployment is a two-phase ha-convert.sh --prepare / --continue flow. This qualifies the single-host “no controller/node tier” statement above — under HA there is a leader (primary) and one or more followers (replicas).
HA is license-gated: the cluster refuses to issue join tokens beyond the licensed node count unless a valid Pro license is active. See Licensing for how the offline license enables it, and High availability for the full architecture, prerequisites, enablement, and day-2 operations.
Related
Section titled “Related”- Quick start — bring the stack up
- AAA policy — how TACACS+ and RADIUS authorization is decided
- NAC overview — endpoints, 802.1X, and port access control
- Network devices — defining the NAS devices every engine relies on
- Deployment bundle & docs: github.com/TaranacLabs/taranac