Securing Jaeger Installation
This page documents the existing security mechanisms in Jaeger, organized by the pairwise connections between Jaeger components.
SDK to Collector
OpenTelemetry SDKs can be configured to communicate directly with Jaeger Collectors via gRPC or HTTP, with optional TLS enabled.
- ✅ HTTP - TLS with mTLS (client cert authentication) supported.
- ✅
gRPC - TLS with mTLS (client cert authentication) supported.
- Covers both span export and sampling configuration querying.
Collector/Ingester/Query to Storage
- ✅ Cassandra - TLS with mTLS (client cert authentication) supported.
- ✅ Elasticsearch - TLS with mTLS (client cert authentication) supported; bearer token propagation.
- ✅ Kafka - TLS with various authentication mechanisms supported (mTLS, Kerberos, plaintext).
- ✅ Prometheus (for SPM) - TLS with mTLS (client cert authentication) supported, as long as you’ve configured your Prometheus server correctly.
| Storage | TLS | mTLS | Basic | Bearer Token | API Key | Kerberos |
|---|---|---|---|---|---|---|
| Cassandra | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Elasticsearch | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| OpenSearch | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Kafka | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| Prometheus | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Browser to UI
- ✅ HTTP - TLS and bearer token authentication (pass-through to storage).
Consumers to Query
- ✅ HTTP - TLS with mTLS (client cert authentication) supported.
- ✅ gRPC - TLS with mTLS (client cert authentication) supported.
AI Agents to Query
The MCP endpoint and the AI Assistant’s
chat endpoint are served by jaeger_query on the query HTTP port. The MCP tools
use the same query service as the UI and the query API, and they return span
attributes, events, and errors. Read-only access can still expose sensitive
telemetry, so treat access to the AI endpoints as access to your trace data.
read_skill also exposes any installation-specific skills.
The query HTTP listener defaults to all interfaces with no TLS and no authenticator. The endpoint shares that listener, so its reachability also depends on your firewall, container port publishing, and ingress configuration.
Production deployment
Require authentication and TLS before making the endpoint reachable by remote
agents. Apply the policy to the entire query HTTP surface, including
<base_path>/api/ai/mcp/ and every path below it, with either an authenticated reverse
proxy or the query server’s own authenticator.
Reverse proxy. Terminate TLS and authenticate requests at the proxy, and
restrict direct access to the query listener so clients cannot bypass it. For a
proxy on the same host, bind extensions.jaeger_query.http.endpoint to
127.0.0.1:16686 and set the upstream Host header to 127.0.0.1 or
localhost, because the MCP transport rejects a loopback request that carries a
public Host header (see Running behind a reverse proxy).
Forward POST, GET, and DELETE requests, the Mcp-Session-Id and
MCP-Protocol-Version headers, and SSE responses without buffering. The query
gRPC listener on :16685 also reads trace data and is not protected by an HTTP
proxy, so bind it to a private address or configure gRPC authentication and TLS.
Query server authenticator. Jaeger includes the collector’s
basicauth server authenticator.
The following settings protect the UI, the query API, the gRPC API, and the MCP
endpoint together. Merge them into your configuration and add basicauth/query
to service.extensions:
extensions:
basicauth/query:
htpasswd:
file: /etc/jaeger/query.htpasswd
jaeger_query:
http:
endpoint: 0.0.0.0:16686
auth:
authenticator: basicauth/query
tls:
cert_file: /etc/jaeger/tls/server.crt
key_file: /etc/jaeger/tls/server.key
min_version: "1.2"
grpc:
endpoint: 0.0.0.0:16685
auth:
authenticator: basicauth/query
tls:
cert_file: /etc/jaeger/tls/server.crt
key_file: /etc/jaeger/tls/server.key
min_version: "1.2"
ai:
mcp:
base_url: https://jaeger.example.com:16686
service:
extensions: [basicauth/query, jaeger_storage, jaeger_query]
Basic authentication requires TLS to protect the credentials in transit.
Configure the MCP client to trust the certificate’s CA and to send the
credentials on every request. With TLS on, Jaeger cannot infer the URL that an
AI chat agent uses to reach the endpoint, so set ai.mcp.base_url to the scheme
and authority the agent can reach; Jaeger appends base_path and the MCP path.
The assistant’s ai.agent_headers authenticate Jaeger’s outbound dial to the
agent; they do not authenticate the agent’s requests back to this endpoint, so
configure the agent’s MCP client with the credentials and CA trust it needs.
Gating the AI surfaces alone
The query server’s http.auth setting applies to every request on the port, so
it cannot admit everyone to the UI while admitting only some people to the AI
features. The ai.auth block takes the same shape and applies it only to the
MCP endpoint, the chat endpoint, and the flag that tells the UI to show the chat
panel. Add the authenticator to service.extensions as well:
extensions:
basicauth/ai:
htpasswd:
inline: |
alice:$2y$...
jaeger_query:
ai:
mcp: {}
auth:
authenticator: basicauth/ai
service:
extensions: [basicauth/ai, jaeger_storage, jaeger_query]
The authenticator receives the request headers, plus any query parameters
listed under ai.auth.request_params. A rejected MCP or chat request is answered with 401. A rejected page request is
served the UI without the chat panel, so a person outside the admitted group
sees Jaeger without the assistant rather than an assistant that fails. Any
extension that implements the collector’s extensionauth.Server interface
works here, so a custom build can gate the features on a group lookup of its
own. Jaeger fails to start when the named extension is not loaded.
The 401 carries no WWW-Authenticate challenge, so a browser never offers basic
credentials on a page load. The example above therefore gates MCP clients and
hides the panel from every browser user. Showing the panel to admitted users
takes an authenticator that reads what a browser sends on navigation, such as a
session cookie or a header that a fronting proxy injects.
The turn-scoped endpoint, /api/ai/mcp/<id>/, that the gateway hands to the
chat agent is not covered. The chat request that created the turn was already
admitted, and only that agent receives the route id.
ai.auth requires Jaeger 2.23 or later.
Tenants and sessions
The endpoint follows extensions.jaeger_query.multi_tenancy. When enabled,
every MCP request must carry the configured tenant header, x-tenant by
default; a missing header or a tenant outside the tenants allowlist receives
HTTP 401, and an empty allowlist accepts any nonempty tenant. A tenant header
selects a tenant and is not proof that the caller may
access it, so enforce that authorization at the proxy.
By default the transport is stateful, and a session keeps the tenant and
identity of the request that created it. Use a separate session for each
identity and tenant, and reconnect when either changes. An Mcp-Session-Id is
not a credential; do not share one across users or tenants. Idle sessions
expire after five minutes.
Forwarding the caller’s identity to the agent
An AI Assistant agent that acts on behalf of the person in the chat, for example to attribute
its model usage to them, needs that person’s identity rather than a credential
of the gateway. The query extension’s header_forwarding list names the request
headers that Jaeger captures from each inbound request and forwards to the
storage backend. The gateway copies the same captured headers onto the dial for
each chat turn, so one list describes the caller’s identity for every hop:
extensions:
jaeger_query:
header_forwarding:
- http_name: X-Identity-Token
ai:
agent_url: ws://127.0.0.1:17000/acp
A listed header that a request does not carry is not sent. Each header arrives
at the agent under its http_outbound_name, or its http_name when that is
unset. A captured header with the same name as an agent_headers entry
replaces the static value. The
gateway copies the header as the request carried it, so it is only as
trustworthy as the proxy in front of Jaeger that sets it and strips any copy the
client supplied. The health probe has no request to copy from, so the agent must
answer initialize without the forwarded header.
