Access log: the pairing poll's ?secret= and a socket's ?ticket= are masked too
This commit is contained in:
@@ -62,21 +62,23 @@ from fluksio.flow.workers import PythonWorkerPool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: A credential in a query string: the live socket's and a worker's `?token=`.
|
||||
_TOKEN_IN_QUERY = re.compile(r"([?&][\w-]*token=)[^&\s]+")
|
||||
#: A credential in a query string: the live socket's `?token=` or `?ticket=`, a
|
||||
#: worker's `?token=`, the pairing poll's `?secret=`.
|
||||
_CREDENTIAL_IN_QUERY = re.compile(r"([?&](?:[\w-]*token|[\w-]*secret|ticket)=)[^&\s]+")
|
||||
|
||||
|
||||
class _RedactTokens(logging.Filter):
|
||||
"""Keep query-string credentials out of uvicorn's request lines.
|
||||
|
||||
uvicorn logs each request and websocket handshake with its query string,
|
||||
and the live socket and a worker's attach carry their token there.
|
||||
and the live socket, a worker's attach and a device polling for its
|
||||
pairing carry their credential there.
|
||||
"""
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
if isinstance(record.args, tuple):
|
||||
record.args = tuple(
|
||||
_TOKEN_IN_QUERY.sub(r"\1***", arg) if isinstance(arg, str) else arg
|
||||
_CREDENTIAL_IN_QUERY.sub(r"\1***", arg) if isinstance(arg, str) else arg
|
||||
for arg in record.args
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""A token in a query string stays out of uvicorn's request lines."""
|
||||
"""A credential in a query string stays out of uvicorn's request lines."""
|
||||
|
||||
import logging
|
||||
|
||||
@@ -26,3 +26,29 @@ def test_a_token_in_the_query_is_not_logged() -> None:
|
||||
assert [record.getMessage() for record in records] == [
|
||||
'192.0.2.1:0 - "WebSocket /api/v1/flows/ws?token=***&x=1" [accepted]'
|
||||
]
|
||||
|
||||
|
||||
def test_the_pairing_secret_is_not_logged() -> None:
|
||||
records: list[logging.LogRecord] = []
|
||||
handler = logging.Handler()
|
||||
handler.emit = records.append # type: ignore[method-assign]
|
||||
log = logging.getLogger("uvicorn.access")
|
||||
level = log.level
|
||||
log.addHandler(handler)
|
||||
log.setLevel(logging.INFO)
|
||||
try:
|
||||
log.info(
|
||||
'%s - "%s %s HTTP/%s" %d',
|
||||
"192.0.2.1:0",
|
||||
"GET",
|
||||
"/api/v1/devices/pair/ABCD2345?secret=s3cr3t",
|
||||
"1.1",
|
||||
202,
|
||||
)
|
||||
finally:
|
||||
log.removeHandler(handler)
|
||||
log.setLevel(level)
|
||||
|
||||
assert [record.getMessage() for record in records] == [
|
||||
'192.0.2.1:0 - "GET /api/v1/devices/pair/ABCD2345?secret=*** HTTP/1.1" 202'
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user