Elevarq Signals authenticates HTTP API callers with bearer tokens. This
document describes both authentication modes — standalone
(default) and managed — including how tokens map to audit
actor identity and how to rotate the control-plane token without
restarting the daemon. Specs: SIGNALS-R011, R083.
| Mode | Tokens recognised | actor mapping |
|---|---|---|
standalone (default) |
api.token |
matched → actor = local_operator |
managed |
api.token, control_plane_token |
api.token → local_operator; control_plane_token → control_plane |
mode is set by signals.mode in signals.yaml or via the
SIGNALS_MODE environment variable. Default is standalone —
operators who don't run the Elevarq control plane don't need to change
anything.
Used for every operator-driven call (CLI, manual curl, automation
scripts the operator runs). Configured the same way it always has
been:
api.tokenfield insignals.yaml, orSIGNALS_API_TOKENenvironment variable, orSIGNALS_API_TOKEN_FILEpointing at a file containing the token.
If none of these are set, Elevarq Signals auto-generates a 32-byte random token at startup and logs the SHA-256 fingerprint (not the value) so the operator can confirm which token is active.
When signals.mode: managed, the daemon also accepts a second
bearer token, distinct from api.token, identified in audit events
as actor = control_plane.
Pick exactly one of these two sources. Setting both at the same time is a hard startup error.
signals:
mode: managed
# Preferred: token file. Re-read on every authentication
# attempt so rotation does not require restart.
control_plane_token_file: /etc/signals/control-plane.token
# Alternative: env var indirection. Treat the env var as the
# source of the token value. Same posture as the existing
# SIGNALS_API_TOKEN_FILE / SIGNALS_API_TOKEN pattern.
# control_plane_token_env: SIGNALS_CONTROL_PLANE_TOKENEquivalent environment-variable overrides:
| Variable | Maps to |
|---|---|
SIGNALS_MODE |
signals.mode |
SIGNALS_CONTROL_PLANE_TOKEN_FILE |
signals.control_plane_token_file |
SIGNALS_CONTROL_PLANE_TOKEN_ENV |
signals.control_plane_token_env |
The control-plane token must:
- Be at least 32 characters long. (Same floor as the auto-
generated
api.token.) - Be distinct from
api.token. The startup check is constant- time. If the two tokens are equal, the daemon refuses to start. - Match the
managedmode setting. Setting only one of the two (mode without a token, or token without the mode) aborts startup.
These checks live in config.ValidateStrict plus
config.ValidateModeBTokens; failures produce an explicit error
message naming the offending field.
| Property | File (_token_file) |
Env var (_token_env) |
|---|---|---|
| Read pattern | os.ReadFile on every authentication attempt. |
os.LookupEnv on every authentication attempt — but the daemon process inherits its env at start; updating the env outside the process does not change what os.LookupEnv returns. |
| Rotation without restart | Yes — operator overwrites the file; the next request reads the new value. | No — env values are captured into the process at fork; the operator must restart the daemon to pick up a new value. |
| Value at rest on disk | Yes — operator restricts file mode (recommended 0600). |
No — value lives in process memory only. |
| Best fit | Long-running deployments where rotation matters; secret managers that mount tokens as files. | Short-lived containers where the orchestrator injects the value once at start and the unit is replaced wholesale on rotation. |
The _env source is therefore best read as "read this env var
once at start". Operators who treat env vars as rotatable need
to either (a) switch to _token_file, or (b) recycle the daemon as
the rotation mechanism — os.LookupEnv does not observe external
env-var mutations during the process lifetime.
The file-based source is re-read on every authentication attempt. Rotation is therefore a single file write:
echo "new-token-value-…" > /etc/signals/control-plane.token
chmod 600 /etc/signals/control-plane.tokenThe next request authenticates against the new token. Any client
still presenting the previous token receives 401 Unauthorized.
If the file is rotated to an empty value, the daemon emits a
slog.Warn (control_plane_token resolved to empty value) and
control-plane authentication degrades to "no token" — the
control-plane caller's requests start receiving 401s. This is
visible in the daemon log so the rotation breakage is observable.
There is no token-rotation HTTP endpoint — rotation is a file operation handled by the operator's secret store / orchestrator.
| Symptom | Daemon response | Audit |
|---|---|---|
Missing Authorization header |
401 | (no audit event) |
| Bearer token matches neither configured token | 401 + R024 rate limiter records the failure | (no audit event in R083) |
Bearer matches api.token |
200/202/4xx as the handler decides | event carries actor=local_operator |
Bearer matches control_plane_token (Mode B) |
200/202/4xx as the handler decides | event carries actor=control_plane |
The R024 per-IP rate limiter blocks an IP after a configured number of failed attempts in a window. That behaviour is unchanged from Phase 2.
R083 deliberately does not emit per-failure audit events. Auth
attempts can be noisy under bot scanning; explicit auth_failed
records are deferred to a future audit-completeness pass with their
own rate limiting.
- Both tokens compared in constant time (
crypto/subtle). - Token values never logged. The R078 audit-attribute denylist
blocks any audit attribute key containing
password,secret,token,dsn,connection_string,payload, orquery_result. A small allow-list permits the booleancontrol_plane_token_configured(no value content) on themode_configuredstartup event. - No token in error messages. Resolution errors carry the configured path or env var name, never the file contents.
- No
control_planeactor without the mode. Inmode=standalone, thecontrol_plane_tokenconfig (if present) is ignored at auth time. A request that would have matched the control-plane token simply gets a 401, identical to any other unknown token. There is no way for a request to acquire the privileged actor identity except by holding the control-plane token AND running inmanagedmode.
signals.yaml:
signals:
mode: managed
control_plane_token_file: /etc/signals/control-plane.token
api:
listen_addr: 127.0.0.1:8081Token file (mode 0600, owned by the daemon user):
$ cat /etc/signals/control-plane.token
01HXY9QZK5T8M3FN6JBPRWADCV7E2GH4
Startup audit event (token VALUE never logged):
audit_event=mode_configured mode=managed control_plane_token_configured=true
Subsequent requests:
# As the local operator (api.token):
curl -H "Authorization: Bearer ${SIGNALS_API_TOKEN}" http://127.0.0.1:8081/status
# audit: actor=local_operator
# As the Elevarq control plane (control_plane_token):
curl -H "Authorization: Bearer 01HXY9QZK5T8M3FN6JBPRWADCV7E2GH4" \
-X POST http://127.0.0.1:8081/collect/now \
-H 'Content-Type: application/json' \
-d '{"targets":["prod-main"], "request_id":"01J5K…", "reason":"automated"}'
# audit: actor=control_plane- No mTLS / signed JWTs / OIDC. Higher-strength auth is Phase 4+ work.
- No
managed_onlymode that refuses the local API token in Mode B. The local token remains valid in both modes so operators are never locked out by an Elevarq-side outage. - No license enforcement in Elevarq Signals. Per R082, the collector is open source; commercial value is in the analysis layer, not in obscured collector behaviour.