Dev Blog
Dev Blog
Short notes on building Taranac — new features, demos and behind-the-scenes.
1.2.2 — MS-CHAPv2, and a directory that isn't AD
1.2.1 was a pass over what already existed. 1.2.2 is the opposite: it closes the biggest hole in the product’s identity story, and a few more besides.
The hole was MS-CHAPv2. Plenty of gear will not authenticate an administrator any other way, and plenty of 802.1X supplicants will not put anything else inside the tunnel. Taranac could not do it. That is fixed — for local accounts, for Active Directory accounts, over RADIUS device administration and over 802.1X alike.
Underneath it sits a second change we did not plan on shipping in the same release: the LDAP integration stopped assuming every directory is Active Directory. And alongside both, certificate enrollment works end to end for the first time.
There is also a security section in this release, and it is not decorative. If you are running 1.2.1 or earlier, this is an upgrade worth scheduling.
MikroTik administrators can log in now
A MikroTik RouterOS box sends only MS-CHAPv2 for an administrator login. No PAP, no TACACS+ client at all. Which meant that until this release a RouterOS device could not authenticate against Taranac in any configuration — and that the RouterOS RADIUS template we shipped with a certain amount of fanfare in 1.2.1 was unreachable.
Device-administration RADIUS now speaks MS-CHAPv2. Local accounts are verified inside Taranac. An Active Directory account is verified by a domain controller, because its password hash is unreadable by design — that is what the domain-membership machinery below is for.
There is nothing to switch on. The branch is chosen by what the device actually sends, and a device that sends PAP never enters it.
Which is also why the RADIUS “authentication type” dropdown is gone. It offered PAP / CHAP / MS-CHAPv2 and nothing read the value — the handler authenticated with PAP regardless of what you picked. A control that advertises a capability the product does not have is worse than no control at all.
One RouterOS-specific fix that will show up in your numbers: a successful
MikroTik login no longer writes a phantom failure. RouterOS probes the SSH
“none” method by proving the empty password before it prompts the human, so every
successful administrator login produced a counted bad_password — halving the
brute-force threshold for exactly the devices that need MS-CHAPv2, and topping the
“top failed authentications” widget with the customer’s own admins.
And the same identity over 802.1X
A directory account now authenticates over PEAP/MSCHAPv2 and over EAP-TTLS/MSCHAPv2. Same mechanism, same domain controller, no separate switch.
While we were in there, native EAP-TTLS/MSCHAPv2 — what a supplicant sends when there is no EAP inside the tunnel — was broken for everyone, including a local account typing the correct password. All three EAP-TTLS inner methods work now.
Two things that used to send you looking in the wrong place:
- A rejected server certificate is named as such. An endpoint whose TLS
handshake dies — almost always because it does not trust the RADIUS server
certificate — was logged as PEAP with
auth_failed, which sends an operator after credentials for a certificate problem. The EAP method now comes from what the daemon reports, and an account that cannot use PEAP is told to use EAP-TTLS instead of being reported as a bad password. A missingntlm_authhelper is likewise named rather than surfacing as a wrong password. - A re-authentication no longer loses the session’s identity. An 802.1X
re-auth closes one session and opens the next milliseconds apart on the same
port, which is what made an active session read
LAB\anonymous.
And a correctness fix that is really a security fix: a domain-qualified name is
no longer an alias for a local account. Over 802.1X, ANYTHING\alice and
alice@whatever.invalid all authenticated as the local account alice, and the
log named the bare account either way. The name is now tried exactly as sent, and
a qualified name may only ever resolve to a directory account.
The node joins the domain by itself
Verifying a domain password means being a domain member, and domain membership is traditionally a ticket to somebody else’s team: create a computer object, delegate rights, wait.
Add an LDAP configuration with the MS-CHAPv2 settings and the installation becomes a domain member using the account it already binds with. A domain’s default machine-account quota is what makes this possible. The machine account name is derived so a node re-joins its own object rather than making a new one each time, and checked against the directory so two installations in one domain cannot overwrite each other. It happens once and never repeats.
Around it:
- An MS-CHAPv2 tab on the LDAP configuration — membership per node, the
feature switch, and a join dialog that asks the directory about the credentials
and the target OU before spending them. The two commonest failures — wrong
password, mis-ordered OU path — are named in under a second instead of coming
back as
net ads join’s own diagnostics. At most one LDAP configuration may own the domain, and the refusal names the one that already does. - A twelfth service,
winbind, ships with every installation. It owns the node’s machine account. With no domain configured it starts, reports “not joined” and does nothing — a service you must first install is a service nobody enables, and MS-CHAPv2 is needed precisely by the administrator who discovers the need at the moment their own login fails. - An alert when the domain trust breaks — narrowly: MS-CHAPv2 is on and this node can no longer verify a domain password. A member with the feature deliberately off never pages anyone, and “never joined” is reported as an incomplete setup, not a broken trust.
- Failures are diagnosed, not guessed. Twenty-four
NT_STATUScodes and nine join failures map to a cause and a next action — expired account, logon-hours policy, a dead controller, NTLM disabled by policy, clock skew, a broken machine trust — and each carries whether it counts as a credential attempt, so a rebooting domain controller no longer looks like password guessing.
A directory no longer has to be Active Directory
This one was not on the roadmap for 1.2.2; it fell out of the work above.
The LDAP integration was built for Active Directory and said so only in its code:
bind by UPN, look accounts up by sAMAccountName, track them by objectGUID,
filter on userAccountControl. Every one of those is false for OpenLDAP,
FreeIPA and Google Workspace Secure LDAP — which is why connecting one of
them could not be made to work by configuration alone, no matter how patiently you
edited the fields.
A directory now has a type: Active Directory, OpenLDAP, FreeIPA, Google Workspace, or generic. The type supplies two different things — what the protocol can do there (MS-CHAPv2 and computer sync are Active Directory only, and the UI says so rather than letting you configure something that cannot work), and the schema guesses you are free to overwrite: search filters, attribute maps, the example under each field.
The type is chosen once, when the configuration is created, and cannot be changed afterwards. Each directory imports accounts by its own login attribute and tracks them by its own identifier, so switching would mean converting users already synchronised — better refused than half-done.
Existing installations are untouched. A configuration with no type reads as Active Directory, and the Active Directory defaults are the previous literals character for character. The documentation covers each type, what it can and cannot do, and how OpenLDAP and FreeIPA connections are set up: LDAP directories.
Three long-standing LDAP annoyances went with it:
- An unreachable directory fails instead of hanging. The client read a boolean server pool as “retry forever”, sleeping between rounds — so a directory that did not answer never produced an error; the request simply hung, holding the worker that made it. Including Test connection, which is exactly where you go to find out what is wrong, and inside the RADIUS and TACACS+ daemons, where that loop sat in the thread serving the request.
- A wrong CA file is named instead of returning HTTP 500 on every operation.
- Computer synchronisation says which setting is missing rather than reporting “connection failure” and sending you to check the network, the controller and the certificates while the real cause sat in a container log.
Certificate enrollment works end to end
EST was in the product and unreachable by any supported route: the frontend
proxied only /api/, so /.well-known/est/ handed an enrolling client the web
UI’s HTML, and the only working path was the API’s bare port with an
internal-only certificate.
EST and the revocation lists are now published through the front door, on the installation’s own certificate, and the CRL distribution point fills itself in.
Approval is a queue now, not a wall. Turning on “require approval” used to
stop issuance rather than defer it: the request was stored as a certificate with
serial 0 and placeholder dates, no approval screen existed anywhere, and every
client poll appended another row. Requests are their own records, deduplicated by
the request fingerprint across polls, with a queue in the UI and an approval group
that says who gets notified. Deciding is still nac_pki.edit.
You can approve from the email. Members of the approval group get the request by mail with what is being issued and to whom. The link never acts on being opened — mail scanners, Safe Links and messenger previews follow links by themselves, so the GET only renders a page and the decision needs a deliberate confirmation.
A revoked certificate can no longer renew itself. A renewal is checked against the revocation list before it is granted.
One honest limit: renewal authenticated by the client’s own certificate needs a listener that verifies client certificates, and this release does not ship one. Until it does, a renewal authenticates the same way an enrollment does. The mTLS gateway is additive and lands in 1.2.3.
802.1X sessions stop lying about themselves
Filed as bug fixes; if you run 802.1X in production they are the reason to upgrade.
- A CoA “session not found” no longer kills a live session. A switch’s 503 answers “did I find a session matching your identifiers”, not “is anybody on the network” — it usually means the port re-authenticated in place. Taranac used to archive the session and report the disconnect as successful, with no way to bring it back.
- The orphaned-session probe no longer disconnects healthy sessions. It asked “are you still there?” with a real Disconnect-Request, so on hardware that sends no periodic interim accounting — the Cisco default — every healthy session eventually crossed the threshold and was torn down for being healthy. CoA also goes to the port configured for the device instead of a fixed one.
Reauthentication timerreaches the hardware. The value was carried everywhere — model, UI, reports, daemon cache — and serialized nowhere. You typed 3600, saw the “seconds” hint, and got nothing. It is rendered as Session-Timeout plus Termination-Action, per the vendor’s own mapping.- A session the NAS keeps reporting is no longer archived behind your back.
Security
Ten findings, several from adversarial review of our own diffs and several found on live hardware. Named plainly, because the alternative is you finding out some other way.
- Certificate enrollment could be reached with no credential at all. A request carrying two invented headers and nothing else got HTTP 200 and a signed certificate: the endpoint trusted client-identity headers that no proxy in the deployment ever set or stripped, so anyone who could reach it controlled them. Reproduced on a stand; now a 401.
- The token issued after the password but before the second factor was accepted as a session. Same key, same shape as a full access token, and nothing checked what it was for — so a stolen password alone was enough to enroll an attacker’s own second factor over the victim’s and then log in normally. Every token now declares its purpose and every endpoint accepts only the purposes it names, deny by default.
- The Telegram MFA webhook accepted unauthenticated requests. The secret check
began with “if a secret was sent”, so a request without the header passed it
entirely — and a forged one could make the bot disclose which Taranac accounts
are linked to a given Telegram account, into a chat of the caller’s choosing.
/mfa/verifyis now attempt-limited too. - Fetching a CRL was an internal port scanner. Only the URL prefix was checked: no loopback or private-range block, no re-check after a redirect, no size limit, TLS verification disabled outright, and the exception text handed back verbatim — on a schedule. It now validates the target, connects to the address it validated, verifies the certificate, caps the download, and reports failures without describing what it found.
- A TACACS+ rule whose device restriction resolved to nothing applied to every device. An emptied device group, or a device sitting on an FQDN object, turned a narrow rule into a fleet-wide one — and the generated ruleset is the only place that restriction is enforced. The same rule already failed closed in RADIUS, which means one rule had opposite meanings in the two protocols.
- A group’s RBAC model could be assigned by anyone who could edit groups, and the model behind a privileged group was visible to anyone who could view them — so an operator could grant their own group a privileged model and log in again. Assigning a model, and membership of administrative groups, now require administrator rights.
- Removing someone else’s second factor required nothing but the record’s UUID.
It requires
mfa.editnow. - A value containing a newline could append arbitrary directives to a generated daemon configuration. Escaping covered only quotes and backslashes, in all three generators. A mangled value breaks one device; a raw newline breaks the configuration of the whole fleet.
- A network object of type FQDN can no longer be attached to a device. Such an object carries no network, so the device silently dropped out of the RADIUS clients list — it was not authenticated at all — and its TACACS+ rules lost their device restriction entirely.
- Every container that runs nginx is rebuilt on a current one — 1.30.4 — and it
no longer announces its version. The listeners ran 1.27.5 (edge, frontend) and
1.26.3 (captive portal) — branches that ended in 2025 on tags frozen ever since,
so rebuilding those images never brought a single fix, however often we rebuilt
them. Nineteen and eighteen published advisories apply. None is reachable through
the shipped configuration, which uses only proxy, TLS, gzip and
return— but that list grows by itself, and an operator whose scanner flags it should not have to take our word for why it does not matter. All four listeners now suppress the version in theServerheader and on error pages; only the captive portal did before. Thanks to Andrew S, who raised it — this is exactly the kind of report that is easy to shrug off from the inside and obvious from the outside.
Keys and backups
The “Rotate Master Key” button destroyed installations. It re-encrypted the
system settings and nothing else — device keys, enable passwords, the internal
CA’s private keys, NCM credentials, TOTP secrets and the license stayed on the old
key while the new one was swapped in memory — and it answered 200. The endpoint
now refuses.
Rotation is a tool: ./taranac rotate-key, on a stopped system, in a single
transaction. You present the current key, name the new one, a backup is taken,
every encrypted column is re-encrypted, the system starts again. It can be
rehearsed on a dev stack, because a tool with nowhere to practise is a tool nobody
has practised.
A backup can now actually be restored. The archive carries .env, the compose
file and config/ — the database’s secrets are ciphertext whose key lives outside
it — and restoring asks for the five keys that matter. pg_dump gained a timeout,
so a hung dump no longer occupies the single scheduler slot forever while the
dashboard shows the last good archive. And an incomplete archive is no longer
reported as successful: the host-files part was never collected on any
deployment, and the only trace was a manifest field nobody reads — discovered at
restore time, the worst possible moment. It is loud now: audit entry, error log,
and a backup:incomplete alert that clears on the next complete archive.
Smaller things
- The username filter matches a substring, case-insensitively. Filtering the
NAC Auth Log for
ad_user1found nothing when the account had authenticated asad_user1@example.comorEXAMPLE\ad_user1. Applied to NAC Auth Log, NAC Sessions and the AAA logs, with trigram indexes on the seven journal tables so it stays fast on a large archive — 53 ms → 1.5 ms on a 60k-row partition. - A VLAN filter on authorization profiles. “Which profiles drop an endpoint into VLAN 40” no longer means paging through the list.
- The UI downloads only the languages it will show. All seven bundled languages — 3.58 MB of translations — were in the entry chunk, so an English-only installation fetched about 3.2 MB it would never display.
- Tables show an error instead of “no records”. On a failed request the log pages rendered their empty state, so an operator mid-incident read an empty authentication journal as “there were no authentications” — the worst kind of failure, silent and pointing the wrong way.
- Escape closes only the topmost drawer. Pressing it in a third-level form used to close all three and lose what you had typed.
- Syslog forwarding has a ceiling and reports what it drops. The forward window was unbounded, so after any downtime the whole missed range loaded at once and went out one message per record — on a large installation that is not a slowdown but an outage, at the moment the system comes back.
- Failed RADIUS MFA challenges say what actually arrived, so an expired code and a value that was never a code are no longer the same log line.
- The dashboard stops polling while its tab is hidden.
- Group matching on FortiGate. The seeded TACACS+ template named the AV-pair
member_of; a FortiGate asks formemberof, so it was silently dropped and group matching could never succeed. Migration 0019 renames it in existing templates and profiles too.
Upgrade notes
Three things worth reading before you pull:
- The RBAC list lost its Type column and System/Custom filter. No role in a
running system is ever a system role, so the column read “Custom” on every row
and one filter option returned everything while the other returned nothing. If
you built a custom report dataset on the
config_rbac_rolestemplate and picked theis_systemcolumn, it fails validation on its next run and needs its columns re-picked. No seeded dataset uses it. - The Security menu section is gone. Authentication & Sessions moved to System; the Crypto page went with it, since it existed for the one button described above.
- EAP-TEAP is gone from the log filter and the policy schema, having never been implemented — and EAP-TTLS, which is fully supported, is finally rendered and filterable in the log views and the session drawer.
1.2.2 ran 4149 passing, 0 failing on the dedicated runner — unit through
sustained-rate load under HA failover — at 81% backend line coverage, dated
2026-08-05 and commit-pinned. The breakdown ships in the bundle as TESTING.md,
including the eight cases tracked openly as expected-fail.
If you have MikroTik gear, or a directory that was never Active Directory, or 802.1X sessions that behaved strangely on re-authentication — this is your release. Where the platform goes next is on the roadmap.
Self-hosted, artifact-only, Elastic License 2.0.
1.2.1 — more vendors, lighter pages
1.2.0 was a big release — a whole reporting domain — and the natural thing after a big release is to go back over what’s already there. That’s what 1.2.1 is. Three threads run through it: more gear speaks Taranac natively, the Configuration Tracker stops being surprised by very large configurations, and the UI got a lot lighter.
Seven more vendors for device administration
If your fleet is Cisco and Juniper, Taranac has always had you covered. Everyone else was doing more work than they should have.
RADIUS admin-auth templates go from 8 to 15 — MikroTik RouterOS, Arista EOS,
Aruba CX, Nokia SR OS, H3C/Comware, Ruckus/Brocade ICX and Extreme EXOS.
TACACS+ goes from 12 to 16, adding H3C/Comware 7 (roles AV-pair, RBAC), Nokia
SR OS, Ruckus/ICX and Extreme EXOS. Each one carries the right attribute for its
platform — Mikrotik-Group, Arista-AVPair, Aruba-Admin-Role, the Timetra
trio, H3C-Exec-Privilege, Foundry-Privilege-Level — including the ones with an
inverted privilege scale, where 0 is the most privileged and getting it
backwards means handing out full access by accident.
On the NAC side, the vendor dictionary gains Extreme EXOS and Ruckus ICX as 802.1X authenticators, both with CoA port bounce wired up. And the long-mislabelled “Aruba” entry is finally split: Aruba AOS-CX / Controllers and a distinct HP ProCurve, which is a different vendor with a different PEN and was never the same thing.
One upgrade detail worth stating plainly: new built-in vendors now reach an existing install. Seeding used to be all-or-nothing — if the dictionary had ever been seeded, later additions never arrived. It’s per-entry now, so you get the new vendors on boot and nothing you edited gets overwritten.
FortiGate command accounting that shows the command
A FortiGate doesn’t report the command it ran in the command field. It reports a
category — Config, Show, Diagnose, Execute — and puts the actual command
elsewhere. Which meant command accounting from a FortiGate technically worked and
told you nothing: page after page of Config.
Records from a FortiGate now carry the real command, in the log views and in the syslog export. If you have FortiGates and you’d written off their accounting, have another look.
Configurations too big to store are now tracked, not failed
The Configuration Tracker had a hard ceiling. Past it, a capture simply failed — and a device whose config is too large is exactly the device you most want to track.
A text configuration larger than a configurable threshold is now recorded as a version — the change is still detected, still dated, still in the timeline — without storing the body. The hash and size describe the full scrubbed text, so an oversized snapshot deduplicates exactly like a normal one: no phantom versions, no spurious “changed” when nothing did. The configuration page and the version timeline say so plainly rather than showing an empty diff.
The three ceilings are now settings, not constants:
ncm.text_store_max_bytes— 2 MiB by default; past this, text is tracked by hash only.ncm.text_buffer_max_bytes— 64 MiB; the largest text a capture will buffer.ncm.binary_max_bytes— 2 GiB; the largest binary it will download (0= unlimited).
The text buffer ceiling used to be 8 MiB, which made a 20 MB text config fail while a 2 GB firmware image sailed through. The defaults are what the product guarantees; the settings exist so an install that legitimately needs more can go further, knowingly.
And a too-large capture is no longer retried. It used to be classified as a transient error, which was wrong twice over: the retry re-fetches the same oversized payload, so every sweep burned the full retry budget — three extra device logins per configuration, for nothing — and the operator was told a threshold problem was a network problem. It now fails immediately and names the setting to raise.
Large configurations open instantly
Viewing the full text of a configuration with tens of thousands of lines used to freeze the tab. The view is windowed above 2,000 lines now and opens immediately at any size. While we were in there: the diff had a word-wrap toggle and the full-text view didn’t — it does now, and the preference carries across both views.
The UI stopped downloading catalogues it never needed
This is the change most people will feel every day, and it’s boring in the best way: forms and detail pages no longer fetch whole catalogues just to display the name of something you already picked.
Users, groups, TACACS+ and RADIUS profiles, NCM credentials, sources, recipes and configurations, LDAP RBAC assignments, the report builder — all of them now read those names straight from the payload they already received, and search the server through a drawer when you actually need to choose something.
Opening an NCM configuration went from about 27 requests and 37 MB to about 5 requests and 2.5 MB.
There’s a second, quieter win in there. Those catalogue fetches capped out at 100 entries — so past a hundred users, or groups, or profiles, the picker silently hid the rest and you had no way to know. Server-side search doesn’t have a hundredth entry.
Smaller things
- Taranac Push MFA is configurable again. The provider card is enrollable once more, marked Soon while the mobile app rolls out.
- LDAP configurations show up under an RBAC model’s “Used By”. A model referenced by an LDAP configuration’s default role or a group override was invisible to the where-used view and could be deleted, leaving a dangling reference. It’s listed now, and deletion is blocked until the reference is gone — matching how groups already behaved.
- Daemon configuration is re-checked at startup. The reload skip-gate added in 1.2.0 watches for data changes, so after an upgrade that changed how a configuration is generated, the new configuration wasn’t deployed until some unrelated edit or a manual Reload. Each daemon now reconciles its on-disk configuration once per boot. If you’re upgrading from 1.2.0, this is the one that matters.
- “Sources” highlights in the NCM sidebar. It was the one NCM settings page
routed outside
/ncm/settings, so the section never expanded for it on a page refresh. Old bookmarks redirect. - The PKI certificates page no longer loads twice. Selecting a CA fired two identical requests for the certificate list.
Vendor coverage is the kind of thing that’s never finished — there’s always one more box in someone’s rack. If yours isn’t in the list, tell us which platform and which attribute it wants; that’s a small, well-shaped change and it tends to land fast. Where the platform goes next is on the roadmap.
Self-hosted, artifact-only, Elastic License 2.0.
1.2.0 — reports, without writing a single query
Every platform that holds operational data eventually gets asked for reports, and there are two easy ways to answer. Bundle a BI tool and let people build whatever they want — powerful, and now your users have to learn a second product to find out how many logins failed last week. Or ship a fixed list of PDFs — simple, and useless the moment someone needs the same thing for one device group instead of all of them.
We spent most of this release looking for the line between those two, and the answer turned out to be a layer in the middle: datasets.
Template → dataset → report
The model has three levels, and it’s worth two minutes because everything else follows from it.
A template is the lowest level, and it lives in code, not in the database. It’s a hardcoded query plus a declaration of what you’re allowed to change about it — which period windows are offered, which columns can be sorted, what the row limit is. Templates ship with the release and get reviewed like any other code.
A dataset is what you save: a template plus your filter values plus your display choices. One template, many datasets — “TACACS+ authentication” is a template; “TACACS+ authentication, last 7 days, core switches, as a bar chart” is a dataset you name and keep.
A report is a multi-page composition of datasets. One dataset per page, ordered however you like. Everything that goes into a report is a dataset — there’s no second path, which is what keeps the whole thing predictable.
That middle layer is the entire trick. You never write SQL — not a line, not ever — but you’re also not stuck with whatever we imagined. The query is ours; the question is yours.
Two kinds of template, and who owns the look
Templates come in two flavours, and the difference is who controls the presentation.
Presets are the curated ones. We author the layout in code — donut rings, KPI
strips, heatmaps, multi-series trends, hand-placed and pixel-checked. You pick the
period and, where a template offers more than one, the view. AAA overview gives
you the full dashboard or a bare row of totals. NAC access gives you the outcome
overview or the reject forensics. Presets export to PDF, because we own the layout
and it’s a designed page.
Tables are the open ones. The query is still fixed, but the display is yours: show it as a table, or map its columns onto a bar, line, pie or donut chart, pick which columns are visible, sort it, filter it. The template declares each column’s role — dimension or measure — so the chart picker only ever offers you mappings that make sense. Tables export to PDF, CSV and XLSX, because a flat result is exactly what belongs in a spreadsheet.
“Table” doesn’t mean “only a table”, by the way. The same result set is a table, a top-N bar or a pie depending on which columns you point at which channel.
43 templates in the box
Sixteen presets and twenty-seven tables ship in 1.2.0, across every surface the platform already writes to:
- AAA — activity trends, policy and command insights, per-device usage for TACACS+ and RADIUS (including the devices that have gone silent, which is the interesting half), and a cross-surface failure view that puts UI, AAA and NAC login failures side by side as rates rather than raw counts.
- NAC — port-access outcomes with a day-by-hour heatmap, reject forensics, live and historical sessions, and certificate posture bucketed by time to expiry.
- NCM — collection health, change activity across tracked configs, and just-in-time credential behaviour.
- System — configuration inventory, platform health, alert overviews, and the accounts that haven’t logged in for the longest.
- Raw tables — every log surface (TACACS+ authn/authz/acct, RADIUS auth/acct, the audit trail) and every configuration table, as flat exports.
What comes out, and why the preview is honest
Reports are assembled on the server. One HTML document is generated, and that same document feeds both the on-screen preview and the PDF — they’re identical because they’re literally the same bytes. Charts are server-rendered inline SVG, so nothing depends on what your browser felt like doing. PDF rendering runs in a small dedicated service that ships alongside the rest of the stack.
The practical effect: what you see in the preview is what lands in the mailbox. No “looked fine on screen, broke in the export”.
Scheduled delivery, with live windows
Any report or dataset can be put on a schedule and mailed out periodically.
The part worth calling out: the schedule doesn’t freeze the period. A dataset scoped to “last 7 days” stays relative — every delivery resolves that window at the moment it’s generated. You set it up once and it keeps meaning the same thing in six months. Nothing about the data is stored on the schedule; it triggers exactly the same generation path as the export button.
Read-only, and a spot on the dashboard
Reports and datasets support a read-only view, so you can hand someone the numbers without handing them the ability to change what’s being measured — the common case for an auditor or a manager who just needs the monthly figure.
Saved reports also surface as a dashboard widget, so the ones you actually use are one click from the front page.
A note on safety
Since the queries are ours, we get to be strict about them. Filter values are bound parameters, never interpolated into a query string. Sortable columns are a whitelist that’s validated against the query’s actual output at import time, so drift gets caught in CI rather than in production. And there’s a registry of columns that can never be projected under any circumstances — private keys, CSRs, session tokens, verification hashes, just-in-time credential plaintext. A reporting engine that can read everything is a data-exfiltration primitive; this one can’t.
UX sharpening
- Picker drawers apply on click. Choosing members, groups, devices, endpoints or datasets in a selection drawer now takes effect immediately, instead of making you confirm with a separate “apply”. Small change, enormous difference if you do it fifty times a day.
- Password rules live on the backend. Length and complexity are enforced identically everywhere a password is set or changed — the web UI and every login path now agree, because they ask the same authority.
- “Must change password” is honoured at device login. A local account flagged to change its password is now denied TACACS+/RADIUS/NAC sign-in until it does, instead of being blocked only in the web UI. If you use that flag as a control, it’s now actually a control.
- MFA push expiry is audited. An unanswered push approval that times out now records its own event, so “nothing happened” is visible in the trail.
Fixes and quiet improvements
- One bad batch can’t wedge TACACS+ logging. A poison log batch is dropped rather than stalling the entire authentication-log writer.
- Faster config reloads on large inventories. The TACACS+/RADIUS/NAC auto-reload now skips a full regeneration when nothing config-relevant changed, and generation no longer blocks the control-plane event loop — a large cut in idle CPU if you run a lot of devices.
- Correct timezone for scheduled jobs across nodes. The system timezone is resolved without stale caching, so scheduled reports fire at the right local time on every node of a cluster.
- Backups work on a fresh install. Creating a backup on a brand-new install no longer fails with a 500 — the backups volume gets correct ownership at boot.
- Auth settings form. Nested fields and zeroed values persist correctly.
Reporting was the biggest single thing we’ve built since the Configuration Tracker, and the design took longer than the code. If the balance lands wrong for you — a preset you’d rather bend, a table you wish were curated — that’s exactly the kind of note that shapes the next one. Where the platform goes after this is on the roadmap.
Self-hosted, artifact-only, Elastic License 2.0.
1.1.2 — you wrote this one
Before the changelog, the important part: thank you. To everyone who pulled the images, stood Taranac up against real gear, and then took the time to write in when something felt off — this release is mostly yours. Almost every item below started as a message, an issue, or a “hey, is this supposed to…” — and that feedback loop is exactly what a young project lives on. Keep it coming.
Here’s what 1.1.2 changes.
Vendor authorization profiles now deploy exactly as previewed
The headline fix, and the one that mattered most to the people who found it. If you
built a TACACS+ authorization profile on a non-shell vendor template — Juniper
junos-exec, FortiGate, Palo Alto and friends — the per-profile preview looked
perfect, but the deployed daemon config quietly collapsed it into a generic
shell / privilege-15 block. The Policy Tester preview showed the same flattened
version, so the two agreed with each other and disagreed with what you actually
authored.
The daemon config now renders every vendor service and raw attribute, matching the per-profile preview one-to-one. What you see is what the box gets.
LDAP failures that tell you why
A failed LDAP / Active Directory login used to report a flat “invalid credentials” no matter what actually went wrong. Now it names the real cause — wrong password, account locked, disabled, expired, or “must change password.” Less guesswork at the help desk, faster answers for the person locked out.
MFA replay protection, now cluster-wide
A one-time TOTP code is supposed to be exactly that — one time. It turned out that in a multi-worker or multi-node deployment, a code could be replayed inside its short validity window by aiming the retry at a different worker process or cluster node, because “already used” was tracked per process. Used codes now live in a shared store, so a code burned on one node is burned everywhere. If you run Taranac clustered, this one’s worth the upgrade on its own.
The smaller sharp edges
- Correct privilege level on a brand-new user’s first login. A user created moments earlier could authenticate but land on a default privilege level for that first session — the authorization step didn’t yet know about them. It now loads the user the same way login does, so the privilege from the matched policy rule applies immediately.
- Matched policy rule, by ID. The TACACS+ authentication and authorization log detail now show the UUID of the rule that matched, next to its name — a stable handle for correlation and scripting, even after a rule gets renamed.
- New-IP alert emails name the browser. The “sign-in from a new IP” notification used to render an empty Browser row; it now reports the client’s browser (or “Unknown” for header-less API callers).
Self-hosted, artifact-only, Elastic License 2.0. Small, fast, and shaped by the people using it — that’s the cadence we want to keep. Found another rough edge? Tell us; the next one has your name on it too.
Boot it and it's done — Taranac now ships as a VM
Installing Taranac has always been short — download the bundle, run two scripts. But “short” still assumed you had a host ready, Docker installed on it, and outbound access to a registry. For a pilot that’s an afternoon and three places to get something subtly wrong, and for a locked-down segment the registry access alone could be a non-starter.
So Taranac now also ships as a virtual machine. One file, one boot.
Two artifacts, one image
There’s an OVA for VMware — ESXi, vSphere, Workstation, Fusion — and a
QCOW2 of the same appliance for Proxmox VE, libvirt/KVM and plain QEMU. They
aren’t two builds: the QCOW2 is converted from the very OVA we publish, so
whatever we tested is what you run either way. The OVA is exported at hardware
version vmx-13, which keeps it importable all the way back to ESXi 6.5,
and the image carries qemu-guest-agent next to open-vm-tools so it’s equally
at home on either hypervisor.
Both are on the download page, with SHA256 sums published beside them. Check the hash. This image gets root on the box that authenticates your network — that’s exactly the kind of file you verify before you boot it.
The install runs offline
This is the part worth the engineering. Every container image is already baked into the disk. When the wizard installs Taranac, it doesn’t pull anything: no registry, no internet, nothing to mirror. An air-gapped segment that would have needed a whole internal registry story now needs a file copy.
What is not baked in is anything secret. No .env, no master key, no
pre-generated passwords — the installer generates every secret on your box, at
install time. Two appliances deployed from the same file share no credentials.
An appliance that shipped with secrets inside would mean one leaked image
compromises everyone who downloaded it, and that trade was never on the table.
Four questions on the console
Power the VM on and the setup wizard is already there — nothing to log into
first. It asks for the hostname, the network (DHCP or a static address,
prefix, gateway and DNS), the timezone — from the full tzdata list, not a
curated handful of cities — and a password for the taranac login user.
Then it offers to install. Choose Install Taranac, give it the address people will open the admin UI at, an admin username and email, and it goes away and brings the stack up. The last screen hands you the URL, the username and a generated password. Copy it then and there.
Underneath, this is not a third way for Taranac to exist. Inside the appliance is
the ordinary deployment bundle, in /opt/taranac. Every ./taranac command, the
TLS certificate procedure, ./taranac update — all of it works exactly as
documented, because it is the documented thing, pre-deployed.
What it doesn’t do yet
Three honest limits, all of them deliberate rather than forgotten.
The wizard installs a core node — collector, witness and captive-portal roles aren’t in it. Those are still deployed as their own docs describe.
The appliance is not self-updating. OS patching and product upgrades stay on your schedule, the same as any VM you run.
And one to act on: the published images carry SSH host keys baked in, so every appliance deployed from the same file presents the same fingerprint. A future build will regenerate them at first boot and make the password step mandatory rather than skippable. Until then the download page and the guide both carry the three commands that fix it — run them once, right after the wizard finishes, before the box sees production traffic.
Where to start
The appliance guide walks through
both imports with screenshots — including the Proxmox settings that are not
optional, since the image is SeaBIOS rather than UEFI and wants a VirtIO SCSI
controller with the disk on scsi0. Get those wrong and it won’t boot; get them
right and you’re at the wizard in a couple of minutes.
Self-hosted, artifact-only, Elastic License 2.0 — same as always. Now with a shorter road to the first successful authentication.
1.1.1 — ArcSight-ready, and a JIT fix under real load
The Configuration Tracker landed a week ago, and 1.1.1 is the fast follow: one new capability and two fixes that came straight from watching the thing run under real load.
CEF, for the SIEM you already have
Taranac has always forwarded its audit and AAA events over syslog. Now it can speak your SIEM’s native dialect: pick CEF (ArcSight) as the format on a syslog destination and every event ships in Common Event Format, ready for ArcSight, Microsoft Sentinel and the many other tools that parse CEF out of the box — alongside the plain RFC 3164 and RFC 5424 you already had.
The detail that matters for correlation: each event carries the canonical rt
event-time field, so it’s timestamped by when it happened, not when the
collector got around to it — no timezone drift smearing your timeline.
The JIT fix real load asked for
In the 1.1.0 post we made a lot of noise about just-in-time credentials — a password minted for a single collection run, then rotated away. Honest confession: the first cut minted that password per run, and the moment two collections hit the same JIT account at once — exactly what happens when you point one credential at a rack of devices — the second job tripped over the first and failed.
The fix is the version we should have shipped: the minted password is now an account-scoped “warm” lease. Every concurrent collection on that account borrows the same live password and logs in in parallel, and the password is rotated once it goes idle — never yanked out from under an in-flight login. Same security story — no long-lived secret, nothing a human ever sees — now with the concurrency a real fleet actually needs.
Backups that survive an update
A quieter fix, and the kind of quiet you want from infrastructure: the backups directory now lives on a named volume. Recreate the container, pull a new image — your backups are still there, instead of quietly vanishing with the old container. Boring. Correct. Exactly what a backup is supposed to be.
There’s also a small structural tidy-up: for SCP/SFTP sources the remote path now lives on a file recipe, matching how HTTP recipes already work — the source describes the connection, the recipe describes what to fetch. Existing sources migrate themselves on upgrade.
Self-hosted, artifact-only, Elastic License 2.0. Small release, quick turnaround — that’s the cadence we’re aiming for.
1.1.0 — Configuration Tracker ships
A couple of weeks ago we published the shape of Configuration Tracker — a design doc, no code, just locked decisions. Today it’s 1.1.0, and the tracker is real. It backs up, versions and diffs your device configs — and because Taranac is already your AAA server, it does two things a standalone backup tool structurally can’t: it can log in with a throwaway password nobody has ever seen, and it can tell you who typed each change.
Point a tracked config at a device, pick a recipe (which commands to run, plus scrub sets that mask secrets and strip volatile lines before anything is stored), and Taranac captures a versioned snapshot on a schedule or on demand. Every capture is content-hashed and deduped, rendered in a side-by-side diff, and you can mark any version as the baseline — plus add your own labels. A config-status column then flags drift the moment a capture stops matching that baseline, and starter recipes + scrub sets ship for a range of vendors.
Collect over whatever the box speaks
A tracked config reaches its device however that device is happiest to be read:
- SSH and Telnet for CLI gear — run the show/running commands and capture the output.
- SCP and SFTP for kit that hands you a config as a file — pull the blob straight off the device.
- HTTPS (and plain HTTP) for API-driven platforms. And this is the open door: an HTTP recipe carries its own request — path, headers, body — so you can author a custom recipe to fetch essentially any file or endpoint a device exposes, not just a fixed command set. If it answers over an API, you can track it.
Two ways to hold the keys
Device logins live in an encrypted credentials vault. Every stored secret is Fernet-encrypted with the deployment’s master key, per value, at rest — the plaintext never sits on disk and is never returned by the API.
But the vault isn’t the interesting part. The interesting part is not storing a password at all — and this is where being the AAA server changes the game. Taranac doesn’t just use the account it logs in with; it owns it. So a tracked config can authenticate with a just-in-time credential: at collection time Taranac mints a strong, 32-character random password on a managed local account, logs in with it for that one run, and rotates it away the moment the job drains.
Follow what that removes. The service account an automated system uses to reach your whole fleet is the classic soft target — a shared password sitting in a config file, reused for months, one leak away from a fleet-wide compromise. A JIT account has none of those handles:
- Nobody has ever seen it. The password is machine-generated and never displayed — not in the UI, not in logs, not over the API. There is no human copy to phish, paste or forget in a wiki.
- It’s genuinely strong. 32 random characters, not a memorable string a person picked.
- It’s already gone. It rotates on every collection, so a password sniffed off the wire is dead before it’s useful — and tomorrow’s doesn’t exist yet.
For the account that reads your configs, service-account compromise simply stops being a category of risk that applies to you.
Who typed what, delivered
The other AAA dividend: open any change and the AAA Activity drawer shows who logged into that box, which commands they ran, and the session accounting — scoped to the exact window between the two config versions. The diff and the culprit, side by side, no extra plumbing. (Per-command attribution needs TACACS+; a RADIUS-only device still gives you who-logged-in and session accounting — an honest limit, not a bug.)
Collection itself runs through a collector — the embedded one needs no setup and covers the single-site case; a dial-out standalone collector handles segmented and remote networks. That part deserves its own post, and it’ll get one.
Self-hosted, artifact-only, Elastic License 2.0. Same as always: kick the tyres, file issues, tell us what’s rough.
What's next — a config tracker that knows who typed what
With HA landed and 1.0.8 out the door, we’ve started sketching the next big piece — and it’s one of those ideas that only makes sense because of what Taranac already is.
Meet Configuration Tracker: backup, versioning and diff for your network devices’ running configs. On its own that’s a well-trodden space — Oxidized, Unimus, SolarWinds NCM all live there. So why build another? Because Taranac is also your TACACS+/RADIUS server, and that gives it two things the others structurally can’t have.
It knows who typed what. A diff usually tells you a line changed. Because Taranac already collects TACACS+ command accounting, ours can tell you which engineer ran which config commands to cause it — the diff and the culprit, side by side.
It doesn’t need a stored password on every device. Instead of stashing credentials for the whole fleet, it can mint a one-time ephemeral account for a single collection job, validate that login itself, and let it expire. Nothing permanent to leak.
Under the hood it’s Postgres-backed rather than a git repo — which means “show
me every version of this config” is one indexed query instead of a git log
crawl that turns into minutes at a few thousand devices. And because the
snapshots live in the same database, your Patroni/etcd HA replicates them for
free.
Fair warning: this is a design doc, not a shipping feature — no code yet, just locked decisions and a build order. We’re publishing the shape early because the interesting arguments (flat-list-plus-tags vs. folder trees, credential-spray safety when you are the AAA server that could ban yourself) are worth having in the open. More as it takes form.
1.0.8 — 1792 tests and nothing to hide
Four weeks ago we shipped the 1.0.0 release candidate. Today it’s 1.0.8, and the arc between them was mostly one word: high availability. Clustering, auto-failover, etcd with TLS on by default, backup/restore that survives a schema change, per-node dashboards so you can see which member is doing what — a long parade of small, unglamorous fixes that add up to “this thing stays up.”
But shipping infrastructure people log into means asking them to trust it, and “trust me” isn’t a feature. So 1.0.8 also ships a receipt. Every release now runs the full suite on a dedicated runner before the images publish:
- 1792 passing, 0 failing — unit and service logic, full HTTP across every
domain, real
tac_plus-ng/ FreeRADIUS / NAC daemons doing live auth, and sustained-rate load through an HA failover. - 70% backend line coverage, commit-pinned, dated.
- Three known-gap cases tracked openly as expected-fail — not swept under the rug, not counted as green.
That last line matters more than the big number. Anyone can quote a test count;
we’d rather tell you exactly which cases we haven’t nailed yet. The full
breakdown ships in the tarball as TESTING.md — a plain table you can read
before you ever pull an image.
Self-hosted, artifact-only, Elastic License 2.0. Same as always: kick the tyres, file issues, tell us what’s rough.
Learning to fall over gracefully
AAA is the kind of infrastructure nobody notices until it’s gone. When your TACACS+ server takes a nap, engineers can’t log into switches; when RADIUS blinks, 802.1X ports stop letting laptops on. So the request we heard most after the RC was blunt: “what happens when the box dies?”
For the last few weeks the answer has been the whole story. Taranac can now run as a cluster — Postgres streaming replication managed by Patroni, with etcd holding the quorum. Lose the primary and a replica gets elected, no human in the loop. Two nodes plus a tiny witness is enough for safe automatic failover; split-brain is settled by the quorum, not by a clever rule we’d inevitably get wrong.
The trick that makes it calm rather than scary: the auth daemons don’t depend on the database at runtime. They answer from generated config and their own caches. So during a failover the control plane (the editor, the writes) pauses for a moment while a new leader is elected — but authentication keeps answering the whole time. Availability beats freshness when the alternative is a locked-out network.
The bit we’re proudest of is the boring bit: turning a standalone install into
a cluster is ha-convert.sh, adding a node is ha-join.sh, and if you change
your mind, ha-deconvert.sh shrinks it back. No reinstall, no data migration —
the replication-ready database has been shipping to everyone since before HA
existed, waiting quietly for the overlay to land on top.
HA is a paid Pro feature — the honest split is that keeping critical infra alive is exactly the kind of thing worth paying for, and it funds the free core. More on the trade-offs as people put it under real load.
Taranac 1.0.0-rc
The first release candidate is here. Taranac 1.0.0-rc brings the whole platform together:
- AAA — TACACS+ and RADIUS device administration, command authorization, templates and profiles, first-match policy with a built-in tester.
- NAC — 802.1X with EAP-TLS, PEAP, EAP-TTLS/TEAP and MAB; endpoints, NAC policy with VLAN/dACL/redirect, and CoA.
- Captive portal — guest access with flows, SMS and sponsor approval.
- PKI — an internal CA plus AD CS and third-party trust, EST and CRL.
- Identity & MFA — AD/LDAP sync and user-selectable MFA: Taranac MFA push, Telegram, email or TOTP.
Everything is self-hosted in Docker under the Elastic License 2.0. Kick the tyres, file issues, and tell us what’s rough — that’s what an RC is for.
One policy core — the idea behind Taranac
Most networks end up with access policy scattered across boxes: a bit of TACACS+ here, a RADIUS server there, 802.1X configured per switch, and an identity story that never quite lines up. Each piece drifts on its own.
Taranac starts from the opposite idea: one policy core. Every request — whether it’s an engineer logging into a switch over TACACS+, a RADIUS login, or a laptop appearing on a port over 802.1X — is evaluated against the same ordered set of rules, matched on who, where, when and source.
That single source of truth is what makes the rest tractable: consistent decisions across protocols, a built-in tester to prove a rule before you ship it, and an audit trail that reads the same no matter how the request arrived.
This blog is where we’ll write about how that core is built — the wins, the trade-offs, and the occasional yak we had to shave.
From TACACS GUI to Taranac — the long way round
Before there was Taranac, there was tacacsgui.
For more than three years it was my project — a web GUI for TACACS+, built and maintained mostly on evenings and weekends. People ran it in real networks. It did an honest job. And I’m genuinely proud of it.
But if you build one thing for that long, you also collect a long list of “if I ever start over, I’d do this differently.” tacacsgui was TACACS+ and only TACACS+. The data model had grown ring by ring like a tree, and some of those rings I’d have cut back. I knew where the seams should be, where the policy engine wanted to live, how identity and NAC and a captive portal should hang off one core instead of being bolted on. I could see the whole thing.
And then I ran out of road. Not out of ideas — out of time and energy. A rewrite that big is a mountain, and I was one person with a day job. So the new architecture stayed where it was: in my head. I carried it around for a long time, turning it over, refining it in the quiet moments, never quite able to start.
What changed isn’t that I suddenly found the hours. It’s that I found people to build it with — a whole bench of tireless assistants I can think out loud with. Sketch an idea, have it argued back at me, poke holes in the data model, weigh a trade-off at 2am, and then actually write the thing — exactly the way I’d been picturing it all along. The mountain didn’t shrink. I just stopped climbing it alone.
So this is where tacacsgui goes: not abandoned, outgrown. Same instinct — make network access control something a human can actually reason about — but rebuilt from the core out, with room for RADIUS, 802.1X, identity, MFA, PKI and more, all deciding on one engine. New name to match the new shape: Taranac.
If you followed me here from tacacsgui — thank you. You backed the caterpillar. This is the butterfly, and it’s finally out of my head and into code. This blog is where I’ll show my work.
Get the release notes
Release notes go to Taranac account holders. Create a free account and tick “Email me about new releases” — one short email a month, what changed and what to upgrade first. Your account also gets you support tickets and licensing.