diff --git a/spec/Http/HttpSensitiveDataHelperSpec.php b/spec/Http/HttpSensitiveDataHelperSpec.php new file mode 100644 index 0000000..65bfd9e --- /dev/null +++ b/spec/Http/HttpSensitiveDataHelperSpec.php @@ -0,0 +1,29 @@ + + */ + +namespace spec\Instrumentation\Http; + +use PhpSpec\ObjectBehavior; + +class HttpSensitiveDataHelperSpec extends ObjectBehavior +{ + public function it_removes_credentials_from_url(): void + { + $this::filterUrl('https://root:p4ssw0rd@example.com?foo=bar#baz')->shouldReturn('https://example.com?foo=bar#baz'); + } + + public function it_removes_credentials_from_headers(): void + { + $this::filterHeaders([ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer kjfdhsfkjshgskjq', + 'proxy-authorization' => 'Basic gperfbshkdbfzdzl', + ])->shouldReturn([ + 'Content-Type' => 'application/json', + ]); + } +} diff --git a/src/Http/HttpMessageHelper.php b/src/Http/HttpMessageHelper.php new file mode 100644 index 0000000..dc4ad4a --- /dev/null +++ b/src/Http/HttpMessageHelper.php @@ -0,0 +1,30 @@ + + */ + +namespace Instrumentation\Http; + +class HttpMessageHelper +{ + /** + * @param array $headers + */ + public static function formatHeadersForSpanAttribute(array $headers): string + { + $headers = HttpSensitiveDataHelper::filterHeaders($headers); + + $lines = []; + foreach ($headers as $name => $values) { + foreach ($values as $value) { + $lines[] = sprintf('%s: %s', mb_strtolower($name), $value); + } + } + + return implode(\PHP_EOL, $lines); + } +} diff --git a/src/Http/HttpSensitiveDataHelper.php b/src/Http/HttpSensitiveDataHelper.php new file mode 100644 index 0000000..0a25d67 --- /dev/null +++ b/src/Http/HttpSensitiveDataHelper.php @@ -0,0 +1,40 @@ + + */ + +namespace Instrumentation\Http; + +use Nyholm\Psr7\Uri; + +class HttpSensitiveDataHelper +{ + private const SENSITIVE_HEADERS = [ + 'authorization', + 'Authorization', + 'proxy-authorization', + 'Proxy-Authorization', + ]; + + public static function filterUrl(string $url): string + { + $url = new Uri($url); + $url = $url->withUserInfo(''); + + return (string) $url; + } + + /** + * @param array $headers + * + * @return array + */ + public static function filterHeaders(array $headers): array + { + return array_diff_key($headers, array_flip(self::SENSITIVE_HEADERS)); + } +} diff --git a/src/Http/TracedResponse.php b/src/Http/TracedResponse.php index 988768f..7f15dce 100644 --- a/src/Http/TracedResponse.php +++ b/src/Http/TracedResponse.php @@ -51,29 +51,6 @@ public function getHeaders(bool $throw = true): array return $this->response->getHeaders($throw); } - private function toReadableHeaderValue(mixed $value): string - { - if (null === $value) { - return 'null'; - } elseif (\is_array($value)) { - return implode(', ', array_map([$this, __FUNCTION__], $value)); - } elseif (\is_scalar($value)) { - if (\is_bool($value)) { - return true === $value ? 'true' : 'false'; - } - - return (string) $value; - } elseif (\is_object($value)) { - if (method_exists($value, '__toString')) { - return (string) $value; - } - - return '(object)#'.$value::class; - } - - return \gettype($value); - } - public function getContent(bool $throw = true): string { try { @@ -182,13 +159,7 @@ protected function endTracing(): void try { if (\in_array('response.headers', $info['user_data']['span_attributes'] ?? [])) { - $headers = []; - $raw = $this->getHeaders(false); - foreach ($raw as $header => $value) { - $headers[$header] = $this->toReadableHeaderValue($value); - } - - $this->span->setAttribute('response.headers', $headers); + $this->span->setAttribute('response.headers', HttpMessageHelper::formatHeadersForSpanAttribute($this->getHeaders(false))); } if (\in_array('response.body', $info['user_data']['span_attributes'] ?? [])) { diff --git a/src/Http/TracingHttpClient.php b/src/Http/TracingHttpClient.php index 6da6f49..5cda904 100644 --- a/src/Http/TracingHttpClient.php +++ b/src/Http/TracingHttpClient.php @@ -93,6 +93,9 @@ public function request(string $method, string $url, array $options = []): Respo if (\in_array('request.body', $options['user_data']['span_attributes'])) { $attributes['request.body'] = self::getRequestBody($options); } + if (\in_array('request.headers', $options['user_data']['span_attributes'])) { + $attributes['request.headers'] = HttpMessageHelper::formatHeadersForSpanAttribute($headers); + } } catch (\Throwable) { } diff --git a/src/Semantics/Attribute/ClientRequestAttributeProvider.php b/src/Semantics/Attribute/ClientRequestAttributeProvider.php index c4d24bb..1f3c73e 100644 --- a/src/Semantics/Attribute/ClientRequestAttributeProvider.php +++ b/src/Semantics/Attribute/ClientRequestAttributeProvider.php @@ -9,6 +9,7 @@ namespace Instrumentation\Semantics\Attribute; +use Instrumentation\Http\HttpSensitiveDataHelper; use OpenTelemetry\SemConv\TraceAttributes; class ClientRequestAttributeProvider implements ClientRequestAttributeProviderInterface @@ -24,7 +25,7 @@ public function getAttributes(string $method, string $url, array $headers = []): { $attributes = [ TraceAttributes::HTTP_METHOD => strtoupper($method), - TraceAttributes::HTTP_URL => $url, + TraceAttributes::HTTP_URL => HttpSensitiveDataHelper::filterUrl($url), ]; foreach ($this->capturedHeaders as $header) {