diff --git a/apisix/plugins/opa.lua b/apisix/plugins/opa.lua index 790ef5f1a9d2..88c3f2328a45 100644 --- a/apisix/plugins/opa.lua +++ b/apisix/plugins/opa.lua @@ -80,6 +80,9 @@ function _M.access(conf, ctx) body = core.json.encode(body), headers = { ["Content-Type"] = "application/json", + -- propagate the trace context (injected by the tracing plugins + -- if enabled) so OPA spans join the request trace + ["traceparent"] = core.request.header(ctx, "traceparent"), }, keepalive = conf.keepalive, ssl_verify = conf.ssl_verify diff --git a/docs/en/latest/plugins/opa.md b/docs/en/latest/plugins/opa.md index fc702355ee33..195c02a9775a 100644 --- a/docs/en/latest/plugins/opa.md +++ b/docs/en/latest/plugins/opa.md @@ -41,6 +41,8 @@ The `opa` Plugin supports the integration with [Open Policy Agent (OPA)](https:/ Once configured, the OPA engine will evaluate the client request to a protected Route to determine whether the request should have access to the Upstream resource based on the defined policies. +If the request has a W3C `traceparent` header, for example one injected by the [`opentelemetry`](./opentelemetry.md) Plugin, the Plugin forwards it to OPA so that OPA's spans join the same trace. + ## Attributes | Name | Type | Required | Default | Valid values | Description | diff --git a/docs/zh/latest/plugins/opa.md b/docs/zh/latest/plugins/opa.md index 0e9c1e0d569e..84e2ad8ff9b8 100644 --- a/docs/zh/latest/plugins/opa.md +++ b/docs/zh/latest/plugins/opa.md @@ -41,6 +41,8 @@ import TabItem from '@theme/TabItem'; 配置后,OPA 引擎将根据定义的策略评估客户端对受保护路由的请求,以决定是否允许其访问上游资源。 +如果请求携带 W3C `traceparent` 请求头(例如由 [`opentelemetry`](./opentelemetry.md) 插件注入),插件会将其转发给 OPA,使 OPA 的 span 加入同一条链路。 + ## 属性 | 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 | diff --git a/t/plugin/opa4.t b/t/plugin/opa4.t new file mode 100644 index 000000000000..43bd67fb8152 --- /dev/null +++ b/t/plugin/opa4.t @@ -0,0 +1,174 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } + + my $extra_yaml_config = <<_EOC_; +plugins: + - opa + - opentelemetry +_EOC_ + $block->set_value("extra_yaml_config", $extra_yaml_config); + + # mock OPA: deny and echo back the traceparent header it received + my $config = $block->config // ""; + $config .= <<_EOC_; + location /v1/data/mock { + content_by_lua_block { + local core = require("apisix.core") + ngx.say(core.json.encode({result = { + allow = false, + reason = ngx.var.http_traceparent or "none", + }})) + } + } +_EOC_ + $block->set_value("config", $config); +}); + +run_tests(); + +__DATA__ + +=== TEST 1: setup route with opa +--- 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, + [[{ + "plugins": { + "opa": { + "host": "http://127.0.0.1:1984", + "policy": "mock" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 2: client traceparent is forwarded to OPA +--- request +GET /hello +--- more_headers +traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 +--- error_code: 403 +--- response_body chomp +00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 + + + +=== TEST 3: no traceparent header is sent when there is none +--- request +GET /hello +--- error_code: 403 +--- response_body chomp +none + + + +=== TEST 4: setup route with opa and opentelemetry +--- 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, + [[{ + "collector": { + "address": "127.0.0.1:4318" + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + + code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "opa": { + "host": "http://127.0.0.1:1984", + "policy": "mock" + }, + "opentelemetry": { + "sampler": { + "name": "always_off" + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 5: OPA receives the APISIX span as parent, not the client one +--- request +GET /hello +--- more_headers +traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 +--- error_code: 403 +--- response_body_like eval +qr/^00-0af7651916cd43dd8448eb211c80319c-(?!b7ad6b7169203331)[0-9a-f]{16}-0[01]$/