Skip to content

Commit 161e202

Browse files
authored
fix: redact the Authorization HTTP header from log (#462)
1 parent 932ff0a commit 161e202

File tree

9 files changed

+93
-13
lines changed

9 files changed

+93
-13
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 1.31.0 [unreleased]
22

3+
### Bug Fixes
4+
1. [#462](https://github.com/influxdata/influxdb-client-python/pull/462): Redact the `Authorization` HTTP header from log
5+
36
## 1.30.0 [2022-06-24]
47

58
### Features

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,7 @@ The client uses uses Python's `logging <https://docs.python.org/3/library/loggin
15871587
- ``influxdb_client.client.write.retry``
15881588
- ``influxdb_client.client.write.dataframe_serializer``
15891589
- ``influxdb_client.client.util.multiprocessing_helper``
1590+
- ``influxdb_client.client.http``
15901591
- ``influxdb_client.client.exceptions``
15911592

15921593
The default logging level is `warning` without configured logger output. You can use the standard logger interface to change the log level and handler:

influxdb_client/_async/rest.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,23 @@
1717
import aiohttp
1818

1919
from influxdb_client.rest import ApiException
20+
from influxdb_client.rest import _BaseRESTClient
2021
from influxdb_client.rest import _UTF_8_encoding
2122

2223

2324
async def _on_request_start(session, trace_config_ctx, params):
24-
print(f">>> Request: '{params.method} {params.url}'")
25-
print(f">>> Headers: '{params.headers}'")
25+
_BaseRESTClient.log_request(params.method, params.url)
26+
_BaseRESTClient.log_headers(params.headers, '>>>')
2627

2728

2829
async def _on_request_chunk_sent(session, context, params):
2930
if params.chunk:
30-
print(f">>> Body: {params.chunk}")
31+
_BaseRESTClient.log_body(params.chunk, '>>>')
3132

3233

3334
async def _on_request_end(session, trace_config_ctx, params):
34-
print(f"<<< Response: {params.response.status}")
35-
print(f"<<< Headers: '{params.response.headers}")
35+
_BaseRESTClient.log_response(params.response.status)
36+
_BaseRESTClient.log_headers(params.headers, '<<<')
3637

3738
response_content = params.response.content
3839
data = bytearray()
@@ -42,7 +43,7 @@ async def _on_request_end(session, trace_config_ctx, params):
4243
break
4344
data += chunk
4445
if data:
45-
print(f"<<< Body: {data.decode(_UTF_8_encoding)}")
46+
_BaseRESTClient.log_body(data.decode(_UTF_8_encoding), '<<<')
4647
response_content.unread_data(data=data)
4748

4849

influxdb_client/_sync/rest.py

+14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from urllib.parse import urlencode
2020

2121
from influxdb_client.rest import ApiException
22+
from influxdb_client.rest import _BaseRESTClient
2223

2324
try:
2425
import urllib3
@@ -164,6 +165,11 @@ def request(self, method, url, query_params=None, headers=None,
164165
if 'Content-Type' not in headers:
165166
headers['Content-Type'] = 'application/json'
166167

168+
if self.configuration.debug:
169+
_BaseRESTClient.log_request(method, f"{url}?{urlencode(query_params)}")
170+
_BaseRESTClient.log_headers(headers, '>>>')
171+
_BaseRESTClient.log_body(body, '>>>')
172+
167173
try:
168174
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
169175
if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
@@ -239,6 +245,14 @@ def request(self, method, url, query_params=None, headers=None,
239245
# we need to decode it to string.
240246
r.data = r.data.decode('utf8')
241247

248+
if self.configuration.debug:
249+
_BaseRESTClient.log_response(r.status)
250+
if hasattr(r, 'headers'):
251+
_BaseRESTClient.log_headers(r.headers, '<<<')
252+
if hasattr(r, 'urllib3_response'):
253+
_BaseRESTClient.log_headers(r.urllib3_response.headers, '<<<')
254+
_BaseRESTClient.log_body(r.data, '<<<')
255+
242256
if not 200 <= r.status <= 299:
243257
raise ApiException(http_resp=r)
244258

influxdb_client/client/_base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
'influxdb_client.client.write.retry',
3939
'influxdb_client.client.write.dataframe_serializer',
4040
'influxdb_client.client.util.multiprocessing_helper',
41-
'influxdb_client.client.exceptions'
41+
'influxdb_client.client.http',
42+
'influxdb_client.client.exceptions',
4243
]
4344

4445

influxdb_client/configuration.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from __future__ import absolute_import
1414

1515
import copy
16-
import http.client as httplib
1716
import logging
1817
import multiprocessing
1918
import sys
@@ -164,17 +163,19 @@ def debug(self, value):
164163
self.__debug = value
165164
if self.__debug:
166165
# if debug status is True, turn on debug logging
167-
for _, logger in self.loggers.items():
166+
for name, logger in self.loggers.items():
168167
logger.setLevel(logging.DEBUG)
169-
# turn on httplib debug
170-
httplib.HTTPConnection.debuglevel = 1
168+
if name == 'influxdb_client.client.http':
169+
logger.addHandler(logging.StreamHandler(sys.stdout))
170+
# we use 'influxdb_client.client.http' logger instead of this
171+
# httplib.HTTPConnection.debuglevel = 1
171172
else:
172173
# if debug status is False, turn off debug logging,
173174
# setting log level to default `logging.WARNING`
174175
for _, logger in self.loggers.items():
175176
logger.setLevel(logging.WARNING)
176-
# turn off httplib debug
177-
httplib.HTTPConnection.debuglevel = 0
177+
# we use 'influxdb_client.client.http' logger instead of this
178+
# httplib.HTTPConnection.debuglevel = 0
178179

179180
@property
180181
def logger_format(self):

influxdb_client/rest.py

+27
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
from __future__ import absolute_import
1313

14+
import logging
15+
from typing import Dict
16+
1417
from influxdb_client.client.exceptions import InfluxDBError
1518
from influxdb_client.configuration import Configuration
1619

@@ -52,6 +55,30 @@ def __str__(self):
5255
return error_message
5356

5457

58+
class _BaseRESTClient(object):
59+
logger = logging.getLogger('influxdb_client.client.http')
60+
61+
@staticmethod
62+
def log_request(method: str, url: str):
63+
_BaseRESTClient.logger.debug(f">>> Request: '{method} {url}'")
64+
65+
@staticmethod
66+
def log_response(status: str):
67+
_BaseRESTClient.logger.debug(f"<<< Response: {status}")
68+
69+
@staticmethod
70+
def log_body(body: object, prefix: str):
71+
_BaseRESTClient.logger.debug(f"{prefix} Body: {body}")
72+
73+
@staticmethod
74+
def log_headers(headers: Dict[str, str], prefix: str):
75+
for key, v in headers.items():
76+
value = v
77+
if 'authorization' == key.lower():
78+
value = '***'
79+
_BaseRESTClient.logger.debug(f"{prefix} {key}: {value}")
80+
81+
5582
def _requires_create_user_session(configuration: Configuration, cookie: str, resource_path: str):
5683
_unauthorized = ['/api/v2/signin', '/api/v2/signout']
5784
return configuration.username and configuration.password and not cookie and resource_path not in _unauthorized

tests/test_InfluxDBClient.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import http.server
22
import json
3+
import logging
34
import os
45
import threading
56
import unittest
7+
from io import StringIO
68

79
import httpretty
810
import pytest
@@ -262,6 +264,21 @@ def test_init_without_token(self):
262264
self.assertIsNotNone(self.influxdb_client)
263265
self.influxdb_client.query_api().query("buckets()", "my-org")
264266

267+
def test_redacted_auth_header(self):
268+
httpretty.register_uri(httpretty.POST, uri="http://localhost/api/v2/query", status=200, body="")
269+
self.influxdb_client = InfluxDBClient("http://localhost", "my-token", debug=True)
270+
271+
log_stream = StringIO()
272+
logger = logging.getLogger("influxdb_client.client.http")
273+
logger.addHandler(logging.StreamHandler(log_stream))
274+
275+
self.influxdb_client.query_api().query("buckets()", "my-org")
276+
requests = httpretty.httpretty.latest_requests
277+
self.assertEqual(1, len(requests))
278+
self.assertEqual("Token my-token", requests[0].headers["Authorization"])
279+
280+
self.assertIn("Authorization: ***", log_stream.getvalue())
281+
265282

266283
class ServerWithSelfSingedSSL(http.server.SimpleHTTPRequestHandler):
267284
def _set_headers(self):

tests/test_InfluxDBClientAsync.py

+15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
2+
import logging
23
import unittest
34
import os
45
from datetime import datetime
6+
from io import StringIO
57

68
import pytest
79
from aioresponses import aioresponses
@@ -247,6 +249,19 @@ async def test_init_without_token(self, mocked):
247249
self.client = InfluxDBClientAsync("http://localhost")
248250
await self.client.query_api().query("buckets()", "my-org")
249251

252+
@async_test
253+
async def test_redacted_auth_header(self):
254+
await self.client.close()
255+
self.client = InfluxDBClientAsync(url="http://localhost:8086", token="my-token", org="my-org", debug=True)
256+
257+
log_stream = StringIO()
258+
logger = logging.getLogger("influxdb_client.client.http")
259+
logger.addHandler(logging.StreamHandler(log_stream))
260+
261+
await self.client.query_api().query("buckets()", "my-org")
262+
263+
self.assertIn("Authorization: ***", log_stream.getvalue())
264+
250265
async def _prepare_data(self, measurement: str):
251266
_point1 = Point(measurement).tag("location", "Prague").field("temperature", 25.3)
252267
_point2 = Point(measurement).tag("location", "New York").field("temperature", 24.3)

0 commit comments

Comments
 (0)