Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/thv-operator/api/v1beta1/mcptelemetryconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ type MCPTelemetryOTelConfig struct {
// +optional
UseLegacyAttributes bool `json:"useLegacyAttributes"`

// EnableUserIDAttribute controls whether the authenticated subject is emitted as the
// OTEL "user.id" span attribute on the inbound MCP server span. Defaults to false because
// the subject can be personally- or tenant-identifying. When enabled, the attribute is only
// set when an authenticated identity is present on the request context, so anonymous requests
// are unaffected. The attribute is high-cardinality and is never added to any metric instrument.
// +kubebuilder:default=false
// +optional
EnableUserIDAttribute bool `json:"enableUserIDAttribute,omitempty"`

// CABundleRef references a ConfigMap containing a CA certificate bundle for the OTLP endpoint.
// When specified, the operator mounts the ConfigMap into the proxyrunner pod and configures
// the OTLP exporters to trust the custom CA. This is useful when the OTLP collector uses
Expand Down
1 change: 1 addition & 0 deletions cmd/thv-operator/pkg/spectoconfig/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func NormalizeMCPTelemetryConfig(
config.Headers = otel.Headers
config.CustomAttributes = otel.ResourceAttributes
config.UseLegacyAttributes = otel.UseLegacyAttributes
config.EnableUserIDAttribute = otel.EnableUserIDAttribute

if otel.Tracing != nil {
config.TracingEnabled = otel.Tracing.Enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var telemetryFieldMappings = []testutil.FieldMapping{
{CRD: "openTelemetry.tracing.samplingRate", Runtime: "samplingRate"},
{CRD: "openTelemetry.metrics.enabled", Runtime: "metricsEnabled"},
{CRD: "openTelemetry.useLegacyAttributes", Runtime: "useLegacyAttributes"},
{CRD: "openTelemetry.enableUserIDAttribute", Runtime: "enableUserIDAttribute"},
{CRD: "prometheus.enabled", Runtime: "enablePrometheusMetricsPath"},
}

Expand Down
45 changes: 32 additions & 13 deletions cmd/thv/app/run_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type RunFlags struct {
OtelEnvironmentVariables []string // renamed binding to otel-env-vars
OtelCustomAttributes string // Custom attributes in key=value format
OtelUseLegacyAttributes bool // Emit legacy attribute names alongside new ones
OtelEnableUserIDAttribute bool // Emit authenticated subject as the user.id span attribute

// Network isolation
IsolateNetwork bool
Expand Down Expand Up @@ -256,6 +257,9 @@ func AddRunFlags(cmd *cobra.Command, config *RunFlags) {
"Custom resource attributes for OpenTelemetry in key=value format (e.g., server_type=prod,region=us-east-1,team=platform)")
cmd.Flags().BoolVar(&config.OtelUseLegacyAttributes, "otel-use-legacy-attributes", true,
"Emit legacy attribute names alongside new OTEL semantic convention names (default true)")
cmd.Flags().BoolVar(&config.OtelEnableUserIDAttribute, "otel-enable-user-id-attribute", false,
"Emit the authenticated subject as the user.id span attribute on the MCP server span "+
"(default false; may expose personally- or tenant-identifying data)")

cmd.Flags().BoolVar(&config.IsolateNetwork, "isolate-network", true,
"Isolate the container network from the host. Use --isolate-network=false to opt out.")
Expand Down Expand Up @@ -422,13 +426,14 @@ func setupTelemetryConfiguration(cmd *cobra.Command, runFlags *RunFlags, appConf
cmd, appConfig, runFlags.OtelEndpoint,
runFlags.OtelSamplingRate, runFlags.OtelEnvironmentVariables, runFlags.OtelInsecure,
runFlags.OtelEnablePrometheusMetricsPath, runFlags.OtelUseLegacyAttributes,
runFlags.OtelEnableUserIDAttribute,
runFlags.OtelTracingEnabled, runFlags.OtelMetricsEnabled)

return createTelemetryConfig(finalTelemetry.OtelEndpoint, finalTelemetry.OtelEnablePrometheusMetricsPath,
runFlags.OtelServiceName, finalTelemetry.OtelTracingEnabled, finalTelemetry.OtelMetricsEnabled,
finalTelemetry.OtelSamplingRate, runFlags.OtelHeaders, finalTelemetry.OtelInsecure,
finalTelemetry.OtelEnvironmentVariables, runFlags.OtelCustomAttributes,
finalTelemetry.OtelUseLegacyAttributes)
finalTelemetry.OtelUseLegacyAttributes, finalTelemetry.OtelEnableUserIDAttribute)
}

// setupRuntimeAndValidation creates container runtime and selects environment variable validator.
Expand Down Expand Up @@ -819,7 +824,7 @@ func configureMiddlewareAndOptions(
runner.WithTelemetryConfigFromFlags(finalOtelEndpoint, runFlags.OtelEnablePrometheusMetricsPath,
finalTracingEnabled, finalMetricsEnabled, runFlags.OtelServiceName,
finalOtelSamplingRate, runFlags.OtelHeaders, runFlags.OtelInsecure, finalOtelEnvironmentVariables,
runFlags.OtelUseLegacyAttributes,
runFlags.OtelUseLegacyAttributes, runFlags.OtelEnableUserIDAttribute,
),
runner.WithToolsFilter(runFlags.ToolsFilter))

Expand Down Expand Up @@ -1068,14 +1073,25 @@ type finalTelemetry struct {
OtelInsecure bool
OtelEnablePrometheusMetricsPath bool
OtelUseLegacyAttributes bool
OtelEnableUserIDAttribute bool
OtelTracingEnabled bool
OtelMetricsEnabled bool
}

// boolFlagOrConfig returns the CLI flag value when the user explicitly set it,
// otherwise it falls back to the supplied config value.
func boolFlagOrConfig(cmd *cobra.Command, name string, flagValue, configValue bool) bool {
if cmd.Flags().Changed(name) {
return flagValue
}
return configValue
}

// getTelemetryFromFlags extracts telemetry configuration from command flags
func getTelemetryFromFlags(cmd *cobra.Command, config *cfg.Config, otelEndpoint string, otelSamplingRate float64,
otelEnvironmentVariables []string, otelInsecure bool, otelEnablePrometheusMetricsPath bool,
otelUseLegacyAttributes bool, otelTracingEnabled bool, otelMetricsEnabled bool) finalTelemetry {
otelUseLegacyAttributes bool, otelEnableUserIDAttribute bool,
otelTracingEnabled bool, otelMetricsEnabled bool) finalTelemetry {
// Use config values as fallbacks for OTEL flags if not explicitly set
finalOtelEndpoint := otelEndpoint
if !cmd.Flags().Changed("otel-endpoint") && config.OTEL.Endpoint != "" {
Expand All @@ -1092,15 +1108,11 @@ func getTelemetryFromFlags(cmd *cobra.Command, config *cfg.Config, otelEndpoint
finalOtelEnvironmentVariables = config.OTEL.EnvVars
}

finalOtelInsecure := otelInsecure
if !cmd.Flags().Changed("otel-insecure") {
finalOtelInsecure = config.OTEL.Insecure
}

finalOtelEnablePrometheusMetricsPath := otelEnablePrometheusMetricsPath
if !cmd.Flags().Changed("otel-enable-prometheus-metrics-path") {
finalOtelEnablePrometheusMetricsPath = config.OTEL.EnablePrometheusMetricsPath
}
// Simple bool flags fall back to the config value whenever the flag was not
// explicitly set on the command line.
finalOtelInsecure := boolFlagOrConfig(cmd, "otel-insecure", otelInsecure, config.OTEL.Insecure)
finalOtelEnablePrometheusMetricsPath := boolFlagOrConfig(
cmd, "otel-enable-prometheus-metrics-path", otelEnablePrometheusMetricsPath, config.OTEL.EnablePrometheusMetricsPath)

finalOtelTracingEnabled := otelTracingEnabled
if !cmd.Flags().Changed("otel-tracing-enabled") && config.OTEL.TracingEnabled != nil {
Expand All @@ -1121,13 +1133,19 @@ func getTelemetryFromFlags(cmd *cobra.Command, config *cfg.Config, otelEndpoint
finalOtelUseLegacyAttributes = *config.OTEL.UseLegacyAttributes
}

// EnableUserIDAttribute defaults to false (opt-in). The config value is used
// as a fallback only when the CLI flag was not explicitly set.
finalOtelEnableUserIDAttribute := boolFlagOrConfig(
cmd, "otel-enable-user-id-attribute", otelEnableUserIDAttribute, config.OTEL.EnableUserIDAttribute)

return finalTelemetry{
OtelEndpoint: finalOtelEndpoint,
OtelSamplingRate: finalOtelSamplingRate,
OtelEnvironmentVariables: finalOtelEnvironmentVariables,
OtelInsecure: finalOtelInsecure,
OtelEnablePrometheusMetricsPath: finalOtelEnablePrometheusMetricsPath,
OtelUseLegacyAttributes: finalOtelUseLegacyAttributes,
OtelEnableUserIDAttribute: finalOtelEnableUserIDAttribute,
OtelTracingEnabled: finalOtelTracingEnabled,
OtelMetricsEnabled: finalOtelMetricsEnabled,
}
Expand Down Expand Up @@ -1163,7 +1181,7 @@ func createOIDCConfig(oidcIssuer, oidcAudience, oidcJwksURL, oidcIntrospectionUR
func createTelemetryConfig(otelEndpoint string, otelEnablePrometheusMetricsPath bool,
otelServiceName string, otelTracingEnabled bool, otelMetricsEnabled bool, otelSamplingRate float64, otelHeaders []string,
otelInsecure bool, otelEnvironmentVariables []string, otelCustomAttributes string,
otelUseLegacyAttributes bool) *telemetry.Config {
otelUseLegacyAttributes bool, otelEnableUserIDAttribute bool) *telemetry.Config {
return runner.BuildTelemetryConfigFromAppConfig(
cfg.OpenTelemetryConfig{
Endpoint: otelEndpoint,
Expand All @@ -1174,6 +1192,7 @@ func createTelemetryConfig(otelEndpoint string, otelEnablePrometheusMetricsPath
Insecure: otelInsecure,
EnablePrometheusMetricsPath: otelEnablePrometheusMetricsPath,
UseLegacyAttributes: &otelUseLegacyAttributes,
EnableUserIDAttribute: otelEnableUserIDAttribute,
},
otelServiceName,
otelHeaders,
Expand Down
6 changes: 4 additions & 2 deletions cmd/thv/app/run_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ func TestBuildRunnerConfig_TelemetryProcessing(t *testing.T) {
tt.runFlags.OtelInsecure,
tt.runFlags.OtelEnablePrometheusMetricsPath,
tt.runFlags.OtelUseLegacyAttributes,
tt.runFlags.OtelEnableUserIDAttribute,
tt.runFlags.OtelTracingEnabled,
tt.runFlags.OtelMetricsEnabled,
)
Expand Down Expand Up @@ -513,6 +514,7 @@ func TestBuildRunnerConfig_TelemetryProcessing_Integration(t *testing.T) {
runFlags.OtelInsecure,
runFlags.OtelEnablePrometheusMetricsPath,
runFlags.OtelUseLegacyAttributes,
runFlags.OtelEnableUserIDAttribute,
runFlags.OtelTracingEnabled,
runFlags.OtelMetricsEnabled,
)
Expand Down Expand Up @@ -584,7 +586,7 @@ func TestCreateTelemetryConfig_DisabledSignals(t *testing.T) {
result := createTelemetryConfig(
tt.endpoint, tt.enablePrometheusMetricsPath,
"test-service", tt.tracingEnabled, tt.metricsEnabled,
1.0, nil, false, nil, "", true,
1.0, nil, false, nil, "", true, false,
)

if tt.expectNil {
Expand Down Expand Up @@ -682,7 +684,7 @@ func TestSetupTelemetryConfiguration_LoadOrCreateConfigPath(t *testing.T) {

result := getTelemetryFromFlags(
cmd, appConfig,
"", 0.0, nil, false, false, false, true, true,
"", 0.0, nil, false, false, false, false, true, true,
)

assert.Equal(t, "https://provider-endpoint.example.com", result.OtelEndpoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: object
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted as the
OTEL "user.id" span attribute on the inbound MCP server span. Defaults to false because
the subject can be personally- or tenant-identifying. When enabled, the attribute is only
set when an authenticated identity is present on the request context, so anonymous requests
are unaffected. The attribute is high-cardinality and is never added to any metric instrument.
type: boolean
enabled:
default: false
description: Enabled controls whether OpenTelemetry is enabled
Expand Down Expand Up @@ -405,6 +414,15 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: object
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted as the
OTEL "user.id" span attribute on the inbound MCP server span. Defaults to false because
the subject can be personally- or tenant-identifying. When enabled, the attribute is only
set when an authenticated identity is present on the request context, so anonymous requests
are unaffected. The attribute is high-cardinality and is never added to any metric instrument.
type: boolean
enabled:
default: false
description: Enabled controls whether OpenTelemetry is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2505,6 +2505,20 @@ spec:
The metrics are served on the main transport port at /metrics.
This is separate from OTLP metrics which are sent to the Endpoint.
type: boolean
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted
as the OTEL "user.id" span attribute on the inbound MCP server span.
When false (the default), no user attribution lands on spans and behavior is
unchanged. When true, "user.id" is set from the authenticated identity's
Subject only when an identity is present on the request context, so
anonymous requests are unaffected.

Defaults to false because the subject can be personally- or
tenant-identifying. The attribute is high-cardinality and is intentionally
never added to any metric instrument.
type: boolean
endpoint:
description: Endpoint is the OTLP endpoint URL
type: string
Expand Down Expand Up @@ -5703,6 +5717,20 @@ spec:
The metrics are served on the main transport port at /metrics.
This is separate from OTLP metrics which are sent to the Endpoint.
type: boolean
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted
as the OTEL "user.id" span attribute on the inbound MCP server span.
When false (the default), no user attribution lands on spans and behavior is
unchanged. When true, "user.id" is set from the authenticated identity's
Subject only when an identity is present on the request context, so
anonymous requests are unaffected.

Defaults to false because the subject can be personally- or
tenant-identifying. The attribute is high-cardinality and is intentionally
never added to any metric instrument.
type: boolean
endpoint:
description: Endpoint is the OTLP endpoint URL
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: object
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted as the
OTEL "user.id" span attribute on the inbound MCP server span. Defaults to false because
the subject can be personally- or tenant-identifying. When enabled, the attribute is only
set when an authenticated identity is present on the request context, so anonymous requests
are unaffected. The attribute is high-cardinality and is never added to any metric instrument.
type: boolean
enabled:
default: false
description: Enabled controls whether OpenTelemetry is enabled
Expand Down Expand Up @@ -408,6 +417,15 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: object
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted as the
OTEL "user.id" span attribute on the inbound MCP server span. Defaults to false because
the subject can be personally- or tenant-identifying. When enabled, the attribute is only
set when an authenticated identity is present on the request context, so anonymous requests
are unaffected. The attribute is high-cardinality and is never added to any metric instrument.
type: boolean
enabled:
default: false
description: Enabled controls whether OpenTelemetry is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2508,6 +2508,20 @@ spec:
The metrics are served on the main transport port at /metrics.
This is separate from OTLP metrics which are sent to the Endpoint.
type: boolean
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted
as the OTEL "user.id" span attribute on the inbound MCP server span.
When false (the default), no user attribution lands on spans and behavior is
unchanged. When true, "user.id" is set from the authenticated identity's
Subject only when an identity is present on the request context, so
anonymous requests are unaffected.

Defaults to false because the subject can be personally- or
tenant-identifying. The attribute is high-cardinality and is intentionally
never added to any metric instrument.
type: boolean
endpoint:
description: Endpoint is the OTLP endpoint URL
type: string
Expand Down Expand Up @@ -5706,6 +5720,20 @@ spec:
The metrics are served on the main transport port at /metrics.
This is separate from OTLP metrics which are sent to the Endpoint.
type: boolean
enableUserIDAttribute:
default: false
description: |-
EnableUserIDAttribute controls whether the authenticated subject is emitted
as the OTEL "user.id" span attribute on the inbound MCP server span.
When false (the default), no user attribution lands on spans and behavior is
unchanged. When true, "user.id" is set from the authenticated identity's
Subject only when an identity is present on the request context, so
anonymous requests are unaffected.

Defaults to false because the subject can be personally- or
tenant-identifying. The attribute is high-cardinality and is intentionally
never added to any metric instrument.
type: boolean
endpoint:
description: Endpoint is the OTLP endpoint URL
type: string
Expand Down
Loading
Loading