Skip to content

1.2.5 — what actually happened on the wire

← Blog

1.2.5 — what actually happened on the wire

The support ticket says “TACACS stopped working”. It is a completely fair description and it is almost never enough, because the explanation is not in one place. It is spread across four: the packets on the wire, the service’s own log for those few seconds, the records Taranac wrote at the time, and how the device is configured.

Nobody gathers four things by hand while the problem is happening. So this release collects them with one command — and, because we hold both the traffic and the key, decodes them, so the archive is readable by the person who collected it rather than only by us.

The other half of 1.2.5 is two faults with the same shape as each other: Taranac misreading what a device actually said, and then confidently reporting something that sent the operator in the wrong direction. One of them was also quietly skipping multi-factor authentication.

One command, one archive

Terminal window
./taranac diagnose --protocol tacacs --device 10.0.0.5

Start it, reproduce the problem on the device, press Enter. It writes one .tar.gz under ./diagnostics and prints, in plain words, what it noticed — which is often enough to settle the question without sending the archive anywhere.

--protocol takes tacacs, radius (device administration over RADIUS) or nac (802.1X port access). --duration SEC captures for a fixed time instead of waiting for Enter, for the case where reproducing the problem and watching the terminal are not the same person’s job.

It checks two things before it captures anything, because the answer is frequently already there:

  • Is this device configured in Taranac for this protocol at all? A device record that exists but is disabled, a protocol flag that is off, a device whose shared secret resolves to nothing — each of these is excluded by the configuration generator, so the daemon has no client entry and drops the device’s requests before any policy is evaluated. Each is now named as a finding, in those words.
  • Which ports are the services actually listening on? Not the documented defaults — the running system.

Those two matter more than they sound, because a device whose protocol flag is off and a daemon on a non-default port produce identical symptoms and identical empty captures. Anyone who has collected a diagnostic and got nothing has met one of them.

If several network objects contain the address — a host entry and the subnets around it, each with its own key or inheriting a different one — every candidate is carried, most specific first, and the report says which one the daemon will actually use.

Decoded, because otherwise you cannot read your own capture

A TACACS+ body is obfuscated on the wire. Hand someone a raw pcap of their own TACACS+ traffic and they have a file they cannot read — not because of a secret they lack, but because the format hides it from everyone. Taranac is the one party holding both the traffic and the key, so the decoding happens inside the api container: capture in, text out, the key never leaves.

RADIUS and 802.1X are decoded too, for a different reason. Three things exist only in the capture and in no log anywhere:

  • the attributes Taranac sent back;
  • everything the device sent beyond the handful of values that get stored;
  • requests discarded before they were processed — which is precisely the “traffic is clearly flowing and Taranac says nothing” case people collect diagnostics for in the first place.

Attributes are named using the same dictionary the service itself generates, so the custom entries you added in 1.2.4 are named correctly here as well, rather than showing up as numbers in the one document where you most need to recognise them.

The traffic is captured on the host, not inside the container. The daemon images ship no tcpdump, and installing it into a container does not survive the next recreate. Capturing on the host also sees both the physical interface and the Docker bridge — so “it arrived at the host and never reached the container”, a firewall or NAT problem, is visible rather than merely absent.

What it will not write down

No shared secret is ever written into the archive. This archive is made to be emailed to support, and a key in a mailbox is a key on the internet.

A secret appears only as a 12-character fingerprint — the same one Taranac already uses for key-anomaly reports. That is enough for the single most useful finding available: the decoder reports which of a device’s possible keys was actually used, and a body that decodes under none of them is near-proof that the two sides disagree about the secret. That is both the commonest fault in this area and otherwise invisible, because a mismatched key does not produce an error — the service simply stops answering.

Passwords are never printed either: only whether one was present and how long it was. The generated daemon configuration is included with its secrets masked.

And an empty capture is reported as empty, with the advice not to send it. An archive with no traffic in it cannot show anything, and the round trip to discover that costs a day.

802.1X, split into attempts

An 802.1X capture is not printed as one flat stream. It is split into separate attempts, each with a headline: who, by what method, how it ended, how many rounds it took, and how long.

Two of those endings are outcomes no log can contain, which is the entire argument for reading the wire:

  • An attempt abandoned by the client. This is the shape of a supplicant refusing an untrusted server certificate: the user declines the warning, the supplicant goes quiet, and the server never learns the exchange ended. There is nothing to log, because from the server’s side nothing happened — it is still waiting.
  • A TLS alert, spelled out rather than left in hex: unknown_ca — the client does not trust the server certificate's CA, certificate_expired, and the rest.

Both of those are, in practice, a certificate problem being reported to the operator as an authentication problem — which is where the hours go.

The logs around the one you asked for

Alongside the chosen service’s log, the bundle carries short tails of the other two and of winbind, clearly marked as context.

Winbind is there for a specific reason: it is what actually verifies a domain password. When a domain login fails, the reason is in winbind’s log and nowhere in Taranac’s own. And an operator who collected the wrong protocol — easy to do, and usually only discovered afterwards — still gets a bundle that can say where the traffic really went.

An enable request is no longer a user who does not exist

A switch asking for enable over RADIUS sends no account name. It sends the synthetic $enab15$, which no directory has ever contained.

Taranac looked that up as an ordinary user, failed to find it, and answered user not found — which reads exactly like a wrong password and sends an operator hunting for an account nobody ever created.

There is now a global enable password on Settings → RADIUS, sitting beside the shared secret and behaving the same way: revealed on request with the reveal recorded in the audit log, copied, regenerated, saved only when changed. It is generated at first boot, as the shared secret is, so the feature is never live with a blank password — and it is meant to be changed before it is handed out.

One password serves every privilege level, because the request identifies the device and nobody else. For that same reason multi-factor authentication cannot apply to an escalation, and the interface says so plainly rather than leaving it to be discovered later. Granting the privilege level at login — where the user is known — remains the stronger arrangement, and that has not changed.

Escalations are also visible for what they are in the authentication log now: a badge reading enable → 15 beside the synthetic name, with the request type and requested level in the detail panel. Without it an escalation looks precisely like a failed login by a non-existent account, because every column that would be filled from a user’s record is empty.

This is recognised for Cisco IOS / IOS-XE and Cisco Business, H3C Comware 7 and Eltex ESR, which share the convention. Equipment that sends the real user name for enable — Aruba AOS-S, ASA, AOS-CX 10.15 and later — already worked and is unchanged, as is equipment with no separate escalation step at all.

A PAP login was reported as a failed enable — and skipped MFA

This one is the reason to update.

TACACS+ lets a device send a login either as an interactive dialogue (ASCII) or in a single packet (PAP). Both are ordinary logins, and which one a device uses is its own choice — some send PAP for SSH, Extreme SLX-OS among them.

Taranac told logins and enable requests apart by a marker that, it turns out, means something else entirely: whether the session can hold an interactive dialogue. A PAP login cannot. So every PAP login was taken for an enable request, and two things followed from that.

The visible one: a user typed a correct password at a normal login prompt and was told “Enable authentication failed”.

The one that matters: enable deliberately does not ask for a second factor — correctly, since the request identifies a device rather than a person. So an account whose policy requires MFA was let in on the password alone.

Login and enable are now separated by the request’s own service field — the thing the device sets, and the very thing that makes the daemon treat a request as an enable in the first place. It is independent of how the password was transported, so ASCII, PAP, CHAP and MS-CHAP are all classified correctly.

Who is affected: any installation with devices that authenticate over PAP. Those logins now work, MFA is enforced on them as the policy always said it was, and the authentication journal stops filing them under enable. Enable itself is unchanged, as is anything using ASCII login.

What to do: nothing. The corrected daemon configuration is written out on update. Until it is, logins are treated as logins — the safe direction — and the service log carries a one-time note.

Two smaller things

The authentication log says why a login was rejected in your own language. The reason column printed the service’s internal token: user_not_found, mschap_no_nt_hash, chap_unsupported, domain_time_skew and thirty-odd more, shown to operators exactly as they are written in the code. All 36 now have text in every bundled language. A token from a service newer than the interface still falls through to the raw string rather than leaving the cell blank — an untranslated reason is more use than none.

The updater stopped asking for a sudo password on every single update. It tells you sudo is needed once, to give the api service read access to .env, and then asked again every time — which is the kind of small dishonesty that teaches people to stop reading what a tool says. Two separate causes, both fixed: pinning the new version rewrote .env as a brand-new file, dropping the group that access depends on; and the check meant to skip the work when the file was already correct reached for the privileged command before looking at the file. The promise the message makes is now true.


1.2.5 ran 4473 passing, 0 failing on the dedicated runner — unit through sustained-rate load under HA failover — at 80% backend line coverage, dated 2026-08-13 and commit-pinned. Coverage is down two points from 1.2.4 and the cause is the denominator rather than a thinner suite: the diagnostic collector adds about a thousand statements that the coverage gate’s paths do not reach. Its own tests exist and pass — 31 of them, 22 belonging to the decoder — they are simply not wired into that gate yet, and widening it is its own change rather than something to slip in beside a release. The breakdown ships in the bundle as TESTING.md, including the eight end-to-end cases tracked openly as expected-fail.

If you have ever been asked for “the logs” after an authentication failure and had to decide which of four places to look in, that is now one command. And if you have devices that speak PAP, this release is the one where their logins stop being reported as something they never were. Existing installations update in place with ./taranac update; new ones start from the appliance. Where the platform goes next is on the roadmap.

Self-hosted, artifact-only, Elastic License 2.0.