Security model
This document describes every security mechanism in tsink — TLS, bearer-token authentication, role-based access control (RBAC), OIDC/JWT validation, cluster mTLS, multi-tenant isolation, secret rotation, and audit logging. Security is enforced entirely at the server layer. The embedded library (tsink crate) has no authentication of its own; access control there is the responsibility of the embedding application.
Contents
- Transport security (TLS)
- Authentication — bearer tokens
- Role-based access control (RBAC)
- OIDC / JWT authentication
- Cluster security
- Multi-tenant isolation
- Secret rotation
- Audit logging
- Spoofing-resistant header model
- CLI security flags reference
1. Transport security (TLS)
tsink uses rustls for all TLS — no OpenSSL dependency. To enable TLS on the public listener, supply a PEM certificate and key:--tls-cert/--tls-key nor the cluster mTLS flags are given, the server listens in plain HTTP mode. Plain HTTP is acceptable for development or when TLS is terminated upstream (e.g. by a load balancer).
2. Authentication — bearer tokens
2.1 Request scopes
Every incoming HTTP request is classified into one of four scopes before authentication is checked:| Scope | Path prefix | Access rule |
|---|---|---|
Probe | /healthz, /ready | Always allowed — no token required. |
Internal | /internal/v1/* | Cluster-peer traffic; authenticated at the transport layer by mTLS or the internal bearer token. Not accessible from the public network. |
Admin | /api/v1/admin/* | Requires the admin token (or the public token when no separate admin token is configured). Enabled only with --enable-admin-api. |
Public | everything else | Requires the public token when one is configured. |
2.2 Public and admin tokens
Two independent bearer tokens can be configured:| Role | Flag | Description |
|---|---|---|
| Public | --auth-token / --auth-token-file | Required for all data-plane endpoints (/api/v1/query, /write, /v1/metrics, …). |
| Admin | --admin-auth-token / --admin-auth-token-file | Required for admin endpoints (/api/v1/admin/*). Falls back to the public token if no dedicated admin token is set. |
Authorization header:
401 Unauthorized. Presenting a public token to an admin endpoint when a separate admin token is configured returns 403 Forbidden (auth_scope_denied).
When no public token is configured, the public endpoints are unauthenticated. Securing the listener with TLS is still strongly recommended even without a token.
3. Role-based access control (RBAC)
RBAC is an optional layer on top of bearer-token authentication. Enable it by pointing--rbac-config at a JSON file. When an RBAC config is present every request is additionally authorised against the role graph; when it is absent the simple token check described in §2 applies.
3.1 Config file structure
POST /api/v1/admin/rbac/reload.
3.2 Roles and grants
A role is a named set of grants. Each grant pairs an action with a resource:| Field | Values |
|---|---|
action | Read or Write |
resource.kind | Tenant — scoped to data-plane operations for a named tenant. Admin — administrative operations. System — system-level operations. |
resource.name | Exact name, prefix wildcard (metrics*), or * for all resources of that kind. |
ops tenant only:
3.3 Principals
A principal is a named identity backed by a static bearer token. Principals are defined in the RBAC config file and are typically used for long-lived service identities.scopes. Omitting scopes means the binding applies to all resources the role grants access to.
3.4 Service accounts
Service accounts are managed programmatically through the admin API and support token rotation without reloading the config file:tsa_ and are generated from 32 cryptographically random bytes (base64url, no padding).
3.5 Authorization flow
When a request arrives and RBAC is active:- The bearer token is extracted from the
Authorizationheader. - An O(1) lookup in the token index resolves the token to a principal or service account identity.
- If no match is found and the token contains two
.separators it is treated as a JWT → OIDC path (see §4). - Otherwise the server returns
401 auth_token_invalid. - If the resolved identity is disabled the server returns
403 auth_principal_disabled. - The binding graph is walked; if any role grant covers the requested action and resource the request is admitted and the identity is stamped onto the request context.
- If no grant matches the server returns
403 auth_scope_denied.
4. OIDC / JWT authentication
OIDC providers are declared in the RBAC config file’soidc_providers array. This allows identity tokens issued by external providers (Keycloak, Okta, Auth0, Google, etc.) to be used as bearer tokens.
4.1 Provider configuration
| Field | Required | Description |
|---|---|---|
name | Yes | Unique provider identifier used in audit entries and the derived principal ID. |
issuer | Yes | Expected iss claim value. |
audiences | No | If non-empty, at least one value must match the JWT aud claim. |
username_claim | No | Claim to use as the display name; defaults to sub. |
jwks_url | No* | URL of the provider’s JWKS endpoint. Fetched once at config load (5 s timeout). |
jwks | No* | Inline array of JWK objects. Use instead of jwks_url for air-gapped deployments. |
claim_mappings | No | Rules that map JWT claim values to RBAC role bindings. |
jwks_url or jwks must be provided.
4.2 Supported algorithms
JWT signature verification is implemented directly with thering cryptography library. No third-party JWT library is used.
| Algorithm | Key type |
|---|---|
RS256 | RSA PKCS#1 (2048–8192 bit) |
ES256 | ECDSA P-256 |
HS256 | HMAC-SHA256 (symmetric) |
alg field; no algorithm downgrade is possible because only the algorithm that matches the configured key’s type is attempted.
4.3 Claim validation
| Claim | Validation |
|---|---|
exp | Required; token must not be expired. A 60-second clock-skew tolerance is applied. |
nbf | Optional; if present, token must have reached its valid-from time (60-second skew). |
iat | Optional; checked as a sanity bound (60-second skew). |
iss | Must match the issuer field of the provider definition. |
aud | Must contain at least one configured audience value (if audiences are configured). |
401 auth_oidc_token_expired. A token with an unrecognised issuer or algorithm returns 401 auth_token_invalid.
4.4 Claim-to-role mappings
Aclaim_mapping entry matches a single JWT claim against a value pattern and, when it matches, injects RBAC role bindings for the request:
oidc:<provider-name>:<sub>.
5. Cluster security
5.1 Internal mTLS
When clustering is enabled, peer-to-peer RPC traffic on/internal/v1/* can be protected with mutual TLS using a dedicated internal CA:
| Flag | Description |
|---|---|
--cluster-internal-mtls-ca-cert | PEM CA bundle used to verify client certs presented by peer nodes. |
--cluster-internal-mtls-cert | PEM certificate presented by this node when connecting to peers as a client. |
--cluster-internal-mtls-key | Corresponding PEM private key. |
/internal/v1/* requests are rejected at the TLS handshake unless they present a certificate signed by the cluster CA. The public listener and the cluster listener share one acceptor; the client-cert requirement applies only when cluster-internal-mtls is enabled.
5.2 Internal bearer token
As an alternative to mTLS, cluster peers can authenticate to each other with a shared bearer token:/internal/v1/* paths. It is never accepted on public endpoints.
mTLS and the internal bearer token are independent — you can use either, both, or neither (the last option is appropriate when the internal network is trusted, e.g. a private Kubernetes namespace).
5.3 Verified node-ID propagation
When a cluster peer connects over mTLS the server extracts the node ID from the client certificate (Common Name or first DNS SAN label) and stamps it into the internal headerx-tsink-internal-verified-node-id. This header is always stripped from inbound requests before any handler sees it, so it can never be spoofed by an external client.
Similarly, all RBAC-verified identity headers (x-tsink-rbac-verified, x-tsink-auth-principal-id, x-tsink-auth-role, etc.) are stripped on ingress. See §9 for the full list.
6. Multi-tenant isolation
6.1 Tenant identification
Each request carries a tenant identifier in thex-tsink-tenant header. When the header is absent the request is attributed to the default tenant. The tenant ID is injected as the internal label __tsink_tenant__ on every stored series, providing hard data-plane isolation between tenants that share a single server.
6.2 Per-tenant auth tokens
The tenant config file (supplied via--tenant-config) supports per-tenant bearer tokens with granular scope:
Write token for tenant acme is allowed to ingest data into that tenant only; it cannot read data or access other tenants. Per-tenant token auth is evaluated before the global SecurityManager token check.
6.3 Per-tenant request and admission policies
Each tenant can have independent request size limits and concurrency budgets:429 Too Many Requests rather than queuing indefinitely, preventing one tenant’s load from starving others.
7. Secret rotation
All security material — bearer tokens and TLS certificates — can be rotated at runtime without restarting the server. The rotation API is available atPOST /api/v1/admin/security/rotate (requires the admin token and --enable-admin-api).
7.1 Rotation targets
| Target | Covers |
|---|---|
PublicAuthToken | The public bearer token (--auth-token / --auth-token-file). |
AdminAuthToken | The admin bearer token. |
ClusterInternalAuthToken | The shared internal cluster auth token. |
ListenerTls | The TLS certificate and key for the public listener. |
ClusterInternalMtls | The client certificate and key used for cluster peer connections. |
7.2 Reload vs. rotate
| Mode | Behaviour |
|---|---|
Reload | Re-reads the material from its original source (file or exec command). The new material replaces the current one; the previous value is retained as a fallback during the overlap window. |
Rotate | Invokes the rotateCommand from the exec manifest (or generates a new random token) to produce new material, writes it to disk atomically, then reloads. |
7.3 Material backends
A secret path is interpreted as:- Plain file — the file content is read as the secret value.
- Exec manifest — if the file begins with
{, it is parsed as JSON:
command is executed to load or reload the current value. rotateCommand is executed during a Rotate operation; if empty the target is not rotatable via the exec path. This allows integration with any secrets management system (HashiCorp Vault, AWS Secrets Manager via CLI, etc.).
Atomic file writes during rotation use a .tmp-<timestamp> temporary file followed by rename, ensuring no reader ever sees a partial write.
7.4 Overlap window
When material is replaced (either by reload or rotation) the previous value is kept alive for a configurable overlap period (default 300 seconds). During this window the server accepts both the old and the new credential, enabling zero-downtime rotation when clients are updated gradually. After the overlap window expires the old credential is discarded. The overlap duration can be overridden per rotation call via theoverlap_seconds request field.
8. Audit logging
tsink maintains three independent audit systems.8.1 RBAC audit ring
An in-memory ring buffer of the last 256 authorization decisions is maintained by the RBAC engine. Everyauthorize() call — whether it succeeds or is denied — produces an entry. Administrative operations (service account create/update/rotate/disable, config reload) are also recorded.
Query the ring:
| Field | Description |
|---|---|
sequence | Monotonically increasing event counter. |
timestamp_unix_ms | Event time. |
event | Event type (e.g. Authorize, ServiceAccountCreated, ConfigReloaded). |
outcome | Allow or Deny. |
principal_id | Resolved identity (oidc:<provider>:<sub> for OIDC tokens). |
role | Role that matched (allow path). |
action | Read or Write. |
resource | Resource kind and name. |
code | Error code for denied requests (e.g. auth_scope_denied). |
auth_method | Token, ServiceAccount, or Oidc. |
provider | OIDC provider name (OIDC path only). |
subject | JWT sub claim (OIDC path only). |
detail | Free-text supplementary information. |
8.2 Security audit ring
A separate in-memory ring of the last 128 entries covers all secret lifecycle events:| Field | Description |
|---|---|
sequence | Monotonically increasing event counter. |
timestamp_unix_ms | Event time. |
target | Which secret was affected (PublicAuthToken, ListenerTls, etc.). |
operation | Reload or Rotate. |
outcome | Success or Failure. |
actor | Principal that triggered the operation. |
detail | Error message on failure. |
GET /api/v1/admin/security/audit.
8.3 Cluster audit log
A persistent JSONL file records cluster-level control-plane events. Every record is fsynced to disk on write. Default limits (all tunable via environment variables):| Variable | Default | Description |
|---|---|---|
TSINK_CLUSTER_AUDIT_RETENTION_SECS | 2 592 000 (30 days) | Records older than this are pruned on each append. |
TSINK_CLUSTER_AUDIT_MAX_LOG_BYTES | 134 217 728 (128 MiB) | When exceeded the log is compacted (rewritten without pruned records). |
TSINK_CLUSTER_AUDIT_MAX_QUERY_LIMIT | 1 000 | Maximum records returned per query. |
ClusterAuditRecord contains:
| Field | Description |
|---|---|
id | Unique record ID. |
timestamp_unix_ms | Event time. |
operation | Operation name (e.g. RebalanceStarted, SnapshotCompleted). |
actor.id | Principal that initiated the operation. |
actor.auth_scope | Public or Admin. |
target | Arbitrary JSON payload describing the affected resource. |
outcome.status | Success or Failure. |
outcome.http_status | HTTP status code. |
outcome.error_type | Error classifier on failure. |
9. Spoofing-resistant header model
Several internal headers carry security-sensitive metadata set by the server during request processing. To prevent clients from injecting forged values, all of these headers are stripped from every inbound request before any handler executes.| Header | Set by | Carries |
|---|---|---|
x-tsink-rbac-verified | RBAC engine | Presence flag when RBAC authorisation succeeded. |
x-tsink-auth-principal-id | RBAC engine | Resolved principal or service account ID. |
x-tsink-auth-role | RBAC engine | Matched role name. |
x-tsink-auth-method | RBAC engine | Token, ServiceAccount, or Oidc. |
x-tsink-auth-provider | RBAC engine | OIDC provider name (OIDC path only). |
x-tsink-auth-subject | RBAC engine | JWT sub claim (OIDC path only). |
x-tsink-internal-verified-node-id | Transport layer | Peer node ID extracted from mTLS client certificate CN / SAN. |
x-tsink-public-auth-required | Security layer | Whether a public auth token is configured. |
x-tsink-public-auth-verified | Security layer | Whether the request passed public token check. |
10. CLI security flags reference
| Flag | Type | Description |
|---|---|---|
--tls-cert PATH | Path | PEM certificate file for the public listener. Enables TLS when combined with --tls-key. |
--tls-key PATH | Path | PEM private key for the public listener. |
--auth-token TOKEN | String | Inline public bearer token. Mutually exclusive with --auth-token-file. |
--auth-token-file PATH | Path | Public bearer token loaded from a plain file or exec manifest. |
--admin-auth-token TOKEN | String | Inline admin bearer token. When set, admin endpoints require this token instead of the public token. |
--admin-auth-token-file PATH | Path | Admin bearer token from a plain file or exec manifest. |
--rbac-config PATH | Path | Path to the RBAC JSON configuration file (roles, principals, service accounts, OIDC providers). |
--tenant-config PATH | Path | Path to the multi-tenant JSON configuration file. |
--enable-admin-api | Flag | Unlock the /api/v1/admin/* endpoint group. Off by default. |
--admin-path-prefix PATH | Path | Restrict admin file-system operations (e.g. cert rotation) to files under this prefix. |
--cluster-internal-auth-token TOKEN | String | Shared bearer token for cluster peer RPC. |
--cluster-internal-auth-token-file PATH | Path | Same, from a plain file or exec manifest. |
--cluster-internal-mtls-enabled BOOL | Bool | Enable mutual TLS for /internal/v1/* cluster traffic. |
--cluster-internal-mtls-ca-cert PATH | Path | PEM CA bundle used to verify client certificates from peer nodes. |
--cluster-internal-mtls-cert PATH | Path | PEM client certificate presented by this node on outbound cluster connections. |
--cluster-internal-mtls-key PATH | Path | PEM private key for the cluster client certificate. |