Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/datadog_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ ngx_int_t DatadogContext::on_precontent_phase(ngx_http_request_t *request) {
// inject headers in the precontent phase into the request headers
// These headers will be copied by ngx_http_proxy_create_request on the
// content phase into the outgoing request headers (probably)
RequestTracing &trace = single_trace();
RequestTracing &trace = traces_.front();
dd::Span &span = trace.active_span();
span.set_tag("span.kind", "client");

Expand Down
3 changes: 3 additions & 0 deletions test/cases/auth_requests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NGINX can authenticate requests based on a subrequest to an external server, which is done by configuring 'auth_request <path>' for the protected locations. These tests ensure that our tracing corrrectly handles this configuration.

IMPORTANT: The NGINX configuration under test sets 'log_subrequest on;' to ensure that we create a span for the auth subrequest, which introduces a child span inside the root nginx span.
Empty file.
38 changes: 38 additions & 0 deletions test/cases/auth_requests/conf/http.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# "/datadog-tests" is a directory created by the docker build
# of the nginx test image. It contains the module, the
# nginx config, and "index.html".
load_module /datadog-tests/ngx_http_datadog_module.so;

events {
worker_connections 1024;
}

http {
datadog_agent_url http://agent:8126;

log_subrequest on;

server {
listen 80;

location /http-no-auth {
proxy_pass http://http:8080;
}

location /http {
set $auth_token $http_x_token;
auth_request /auth;

proxy_pass http://http:8080;
}

location =/auth {
internal;
proxy_pass http://http:8080/auth;
proxy_pass_request_body off;
proxy_pass_request_headers off;
proxy_set_header Content-Length "";
proxy_set_header Authorization $auth_token;
}
}
}
154 changes: 154 additions & 0 deletions test/cases/auth_requests/test_auth_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from .. import case
from .. import formats

import json
from pathlib import Path


class TestAuthRequests(case.TestCase):

def test_no_auth_request_is_successful(self):
conf_path = Path(__file__).parent / "./conf/http.conf"
conf_text = conf_path.read_text()
status, log_lines = self.orch.nginx_replace_config(
conf_text, conf_path.name)
self.assertEqual(status, 0, log_lines)

self.orch.sync_service('agent')

path = '/http-no-auth'
status, _, body = self.orch.send_nginx_http_request(path)
self.assertEqual(status, 200)
response = json.loads(body)
self.assertEqual(response["service"], "http")

headers = response["headers"]
trace_id = headers["x-datadog-trace-id"]
span_id = headers["x-datadog-parent-id"]

self.orch.reload_nginx()
log_lines = self.orch.sync_service('agent')

# Expect that nginx sent a trace containing two spans: one for the
# request, and another for the auth subrequest.
nginx_sent_a_trace = False
for line in log_lines:
trace = formats.parse_trace(line)
if trace is None:
# not a trace
continue
for chunk in trace:
first, *rest = chunk
if first['service'] == 'nginx':
nginx_sent_a_trace = True
self.assertEqual(0, len(rest), chunk)
self.assertEqual(first['meta']['http.url'],
f'http://nginx{path}', first)
self.assertEqual(first['meta']['nginx.location'], path,
first)

self.assertEqual(str(first['trace_id']), str(trace_id),
trace)
self.assertEqual(str(first['span_id']), str(span_id),
trace)

self.assertTrue(nginx_sent_a_trace, log_lines)

def test_auth_request_with_auth_token_is_successful(self):
conf_path = Path(__file__).parent / "./conf/http.conf"
conf_text = conf_path.read_text()
status, log_lines = self.orch.nginx_replace_config(
conf_text, conf_path.name)
self.assertEqual(status, 0, log_lines)

self.orch.sync_service('agent')

path = '/http'
status, _, body = self.orch.send_nginx_http_request(
path, 80, headers={'x-token': 'mysecret'})
self.assertEqual(status, 200)
response = json.loads(body)
self.assertEqual(response["service"], "http")

headers = response["headers"]
trace_id = headers["x-datadog-trace-id"]
span_id = headers["x-datadog-parent-id"]

self.orch.reload_nginx()
log_lines = self.orch.sync_service('agent')

# Expect that nginx sent a trace containing two spans: one for the
# request, and another for the auth subrequest.
nginx_sent_a_trace = False
for line in log_lines:
trace = formats.parse_trace(line)
if trace is None:
# not a trace
continue
for chunk in trace:
first, *rest = chunk
if first['service'] == 'nginx':
nginx_sent_a_trace = True
self.assertEqual(1, len(rest), chunk)
self.assertEqual(first['meta']['http.url'],
f'http://nginx{path}', first)
self.assertEqual(first['meta']['nginx.location'], path,
first)

# Assert that the subrequest was traced
self.assertEqual('nginx', rest[0]['service'], rest[0])
self.assertEqual(rest[0]['meta']['http.url'],
f'http://nginx{path}', rest[0])
self.assertEqual(rest[0]['meta']['nginx.location'],
'/auth', rest[0])

# Assert existing behavior that the trace ID and span ID
# match the trace ID and span ID of the nginx subrequest.
self.assertEqual(str(first['trace_id']), str(trace_id),
trace)
self.assertEqual(str(first['span_id']), str(span_id),
trace)

self.assertTrue(nginx_sent_a_trace, log_lines)

def test_auth_request_without_auth_token_is_not_successful(self):
conf_path = Path(__file__).parent / "./conf/http.conf"
conf_text = conf_path.read_text()
status, log_lines = self.orch.nginx_replace_config(
conf_text, conf_path.name)
self.assertEqual(status, 0, log_lines)

self.orch.sync_service('agent')

path = '/http'
status, _, _ = self.orch.send_nginx_http_request(path)
self.assertEqual(status, 401)

self.orch.reload_nginx()
log_lines = self.orch.sync_service('agent')

# Expect that nginx sent a trace containing two spans: one for the
# request, and another for the auth subrequest.
nginx_sent_a_trace = False
for line in log_lines:
trace = formats.parse_trace(line)
if trace is None:
# not a trace
continue
for chunk in trace:
first, *rest = chunk
if first['service'] == 'nginx':
nginx_sent_a_trace = True
self.assertEqual(1, len(rest), chunk)
self.assertEqual(first['meta']['http.url'],
f'http://nginx{path}', first)
self.assertEqual(first['meta']['nginx.location'], path,
first)

self.assertEqual('nginx', rest[0]['service'], rest[0])
self.assertEqual(rest[0]['meta']['http.url'],
'http://nginx/http', rest[0])
self.assertEqual(rest[0]['meta']['nginx.location'],
'/auth', rest[0])

self.assertTrue(nginx_sent_a_trace, log_lines)