This is an automated email from the ASF dual-hosted git repository.

AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new cbd264a31 fix(opentelemetry): validate x-request-id before using it as 
trace_id (#12990)
cbd264a31 is described below

commit cbd264a310732b3a773b7bc2c026d066c60ab6cb
Author: prasun srivastav <[email protected]>
AuthorDate: Fri Jul 10 12:08:13 2026 +0530

    fix(opentelemetry): validate x-request-id before using it as trace_id 
(#12990)
---
 apisix/plugins/opentelemetry.lua |  46 +++++++-
 t/plugin/opentelemetry.t         | 245 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 289 insertions(+), 2 deletions(-)

diff --git a/apisix/plugins/opentelemetry.lua b/apisix/plugins/opentelemetry.lua
index 7ec44edd0..cecf8ba84 100644
--- a/apisix/plugins/opentelemetry.lua
+++ b/apisix/plugins/opentelemetry.lua
@@ -48,6 +48,7 @@ local pairs   = pairs
 local ipairs  = ipairs
 local unpack  = unpack
 local string_format = string.format
+local string_lower = string.lower
 local update_time = ngx.update_time
 local tostring = tostring
 
@@ -57,6 +58,29 @@ local lrucache = core.lrucache.new({
 
 local asterisk = string.byte("*", 1)
 
+-- capture the library's default (random) generator once, so wrapping it below
+-- stays idempotent even when create_tracer_obj re-runs after cache expiry
+local original_new_ids = id_generator.new_ids
+
+
+-- expects an already-lowercased string
+local function is_valid_trace_id(trace_id)
+    if not trace_id or #trace_id ~= 32 then
+        return false
+    end
+
+    if not trace_id:match("^[0-9a-f]+$") then
+        return false
+    end
+
+    -- W3C Trace Context: all-zero trace_id is invalid
+    if trace_id == "00000000000000000000000000000000" then
+        return false
+    end
+
+    return true
+end
+
 local metadata_schema = {
     type = "object",
     properties = {
@@ -231,10 +255,28 @@ end
 
 
 local function create_tracer_obj(conf, plugin_info)
+    -- id_generator is a shared module, so restore the default before applying
+    -- the override: without this, switching trace_id_source back to "random"
+    -- would keep honoring X-Request-Id until the worker restarts
+    id_generator.new_ids = original_new_ids
+
     if plugin_info.trace_id_source == "x-request-id" then
         id_generator.new_ids = function()
-            local trace_id = core.request.headers()["x-request-id"] or 
ngx_var.request_id
-            return trace_id, id_generator.new_span_id()
+            local trace_id = core.request.headers()["x-request-id"]
+                            or ngx_var.request_id
+
+            -- a duplicated X-Request-Id makes get_headers() return a table;
+            -- only a plain, valid 32-hex string can be used as a trace_id,
+            -- anything else (UUID, table, empty, ...) falls back to the
+            -- default generator instead of crashing the otlp encoder
+            if type(trace_id) == "string" then
+                trace_id = string_lower(trace_id)
+                if is_valid_trace_id(trace_id) then
+                    return trace_id, id_generator.new_span_id()
+                end
+            end
+
+            return original_new_ids()
         end
     end
     -- create exporter
diff --git a/t/plugin/opentelemetry.t b/t/plugin/opentelemetry.t
index 8287a99ea..0e2687392 100644
--- a/t/plugin/opentelemetry.t
+++ b/t/plugin/opentelemetry.t
@@ -734,3 +734,248 @@ plugins:
 grep apisix.phase.access ci/pod/otelcol-contrib/data-otlp.json | tail -n 1
 --- response_body eval
 qr/otel-meta-change-second/
+
+
+
+=== TEST 31: reset metadata trace_id_source = x-request-id
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/opentelemetry',
+                ngx.HTTP_PUT,
+                [[{
+                    "batch_span_processor": {
+                        "max_export_batch_size": 1,
+                        "inactive_timeout": 0.5
+                    },
+                    "trace_id_source": "x-request-id",
+                    "resource": {
+                        "service.name": "APISIX"
+                    },
+                    "collector": {
+                        "address": "127.0.0.1:4318",
+                        "request_timeout": 3
+                    }
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 32: reset route for x-request-id validation
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "name": "route_name",
+                    "plugins": {
+                        "opentelemetry": {
+                            "sampler": {
+                                "name": "always_on"
+                            }
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/opentracing"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 33: invalid (UUID) x-request-id should not crash
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
+--- wait: 2
+--- response_body
+opentracing
+--- no_error_log
+[error]
+
+
+
+=== TEST 34: invalid x-request-id still exports a valid random trace id
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"[0-9a-f]{32}"/
+
+
+
+=== TEST 35: all-zero x-request-id must not be used as trace id
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: 00000000000000000000000000000000
+--- wait: 2
+--- response_body
+opentracing
+
+
+
+=== TEST 36: all-zero id is replaced by a non-zero random trace id
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"(?!0{32})[0-9a-f]{32}"/
+
+
+
+=== TEST 37: uppercase 32-hex x-request-id is used
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: 550E8400E29B41D4A716446655440000
+--- wait: 2
+--- response_body
+opentracing
+
+
+
+=== TEST 38: uppercase 32-hex is lowercased and used as trace id
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"550e8400e29b41d4a716446655440000"/
+
+
+
+=== TEST 39: duplicated x-request-id header should not crash
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: 550e8400e29b41d4a716446655440000
+X-Request-Id: aabbccddeeff00112233445566778899
+--- wait: 2
+--- response_body
+opentracing
+--- no_error_log
+[error]
+
+
+
+=== TEST 40: missing x-request-id falls back to default generator
+--- request
+GET /opentracing
+--- wait: 2
+--- response_body
+opentracing
+
+
+
+=== TEST 41: missing x-request-id still exports a valid trace id
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"[0-9a-f]{32}"/
+
+
+
+=== TEST 42: non-hex x-request-id falls back to default generator
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+--- wait: 2
+--- response_body
+opentracing
+
+
+
+=== TEST 43: non-hex x-request-id still exports a valid trace id
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"[0-9a-f]{32}"/
+
+
+
+=== TEST 44: empty x-request-id falls back to default generator
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id:
+--- wait: 2
+--- response_body
+opentracing
+--- no_error_log
+[error]
+
+
+
+=== TEST 45: empty x-request-id still exports a valid trace id
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"[0-9a-f]{32}"/
+
+
+
+=== TEST 46: switch metadata trace_id_source back to random
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/opentelemetry',
+                ngx.HTTP_PUT,
+                [[{
+                    "batch_span_processor": {
+                        "max_export_batch_size": 1,
+                        "inactive_timeout": 0.5
+                    },
+                    "trace_id_source": "random",
+                    "resource": {
+                        "service.name": "APISIX"
+                    },
+                    "collector": {
+                        "address": "127.0.0.1:4318",
+                        "request_timeout": 3
+                    }
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 47: after switching to random, send a valid 32-hex x-request-id
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: 550e8400e29b41d4a716446655440000
+--- wait: 2
+--- response_body
+opentracing
+
+
+
+=== TEST 48: x-request-id must be ignored once trace_id_source is random
+--- exec
+tail -n 1 ci/pod/otelcol-contrib/data-otlp.json
+--- response_body eval
+qr/"traceId"\s*:\s*"(?!550e8400e29b41d4a716446655440000)[0-9a-f]{32}"/

Reply via email to