Skip to content

Commit 31b379b

Browse files
zealwskorikuzmajarbesfeldkatie-perry
authored
fix: Redact auth from DB url in status check (#431)
At present, the status check logs the DB URL, including the password. This PR uses a helper function from pip to redact the password from the URLs, making them safe to log. After this change, this is what the log messages look like: ``` ERROR:cool_seq_tool.resources.status:Encountered error instantiating UTA at URI postgresql://uta_admin:****@localhost:5432/uta/uta_20241220 ``` Closes #429 --------- Co-authored-by: Kori Kuzma <[email protected]> Co-authored-by: jarbesfeld <[email protected]> Co-authored-by: Katie Stahl <[email protected]>
1 parent 7f322c1 commit 31b379b

File tree

3 files changed

+134
-4
lines changed

3 files changed

+134
-4
lines changed

src/cool_seq_tool/resources/status.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
from collections import namedtuple
55
from pathlib import Path
6+
from urllib.parse import urlparse
67

78
from agct._core import ChainfileError
89
from asyncpg import InvalidCatalogNameError, UndefinedTableError
@@ -11,7 +12,7 @@
1112
from cool_seq_tool.handlers.seqrepo_access import SEQREPO_ROOT_DIR, SeqRepoAccess
1213
from cool_seq_tool.mappers.liftover import LiftOver
1314
from cool_seq_tool.resources.data_files import DataFile, get_data_file
14-
from cool_seq_tool.sources.uta_database import UTA_DB_URL, UtaDatabase
15+
from cool_seq_tool.sources.uta_database import UTA_DB_URL, ParseResult, UtaDatabase
1516

1617
_logger = logging.getLogger(__name__)
1718

@@ -119,14 +120,20 @@ async def check_status(
119120
else:
120121
status["liftover"] = True
121122

123+
parsed_result = ParseResult(urlparse(db_url))
124+
sanitized_url = parsed_result.sanitized_url
122125
try:
123126
await UtaDatabase.create(db_url)
127+
except ValueError:
128+
_logger.exception("Database URL is not valid")
124129
except (OSError, InvalidCatalogNameError, UndefinedTableError):
125-
_logger.exception("Encountered error instantiating UTA at URI %s", UTA_DB_URL)
130+
_logger.exception(
131+
"Encountered error instantiating UTA at URI %s", sanitized_url
132+
)
126133
except Exception as e:
127134
_logger.critical(
128135
"Encountered unexpected error instantiating UTA from URI %s: %s",
129-
UTA_DB_URL,
136+
sanitized_url,
130137
e,
131138
)
132139
else:

src/cool_seq_tool/sources/uta_database.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from os import environ
66
from typing import Any, Literal, TypeVar
77
from urllib.parse import ParseResult as UrlLibParseResult
8-
from urllib.parse import unquote, urlparse
8+
from urllib.parse import unquote, urlparse, urlunparse
99

1010
import asyncpg
1111
import boto3
@@ -953,3 +953,28 @@ def schema(self) -> str | None:
953953
"""Create schema property."""
954954
path_elems = self.path.split("/")
955955
return path_elems[2] if len(path_elems) > 2 else None
956+
957+
@property
958+
def sanitized_url(self) -> str:
959+
"""Sanitized DB URL with the password masked"""
960+
netloc = ""
961+
if self.username:
962+
netloc += self.username
963+
if self.password is not None and self.password != "":
964+
netloc += ":***"
965+
netloc += "@"
966+
if self.hostname:
967+
netloc += f"{self.hostname}"
968+
if self.port:
969+
netloc += f":{self.port}"
970+
971+
return urlunparse(
972+
(
973+
self.scheme,
974+
netloc,
975+
self.path,
976+
self.params,
977+
self.query,
978+
self.fragment,
979+
)
980+
)

tests/sources/test_uta_database.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""Test UTA data source."""
22

3+
from urllib.parse import urlparse
4+
35
import pytest
46

57
from cool_seq_tool.schemas import Strand
68
from cool_seq_tool.sources.uta_database import (
79
GenomicTxData,
810
GenomicTxMetadata,
11+
ParseResult,
912
TxExonAlnData,
1013
)
1114

@@ -360,3 +363,98 @@ async def test_get_mane_transcripts_from_genomic_pos(test_db):
360363
# invalid ac
361364
resp = await test_db.get_transcripts_from_genomic_pos("NC_000007.14232", 140753336)
362365
assert resp == []
366+
367+
368+
@pytest.mark.parametrize(
369+
("raw_url", "expected"),
370+
[
371+
# Username + password
372+
(
373+
"postgresql://user:pass@localhost:5432/dbname",
374+
{
375+
"scheme": "postgresql",
376+
"username": "user",
377+
"password": "pass",
378+
"hostname": "localhost",
379+
"port": 5432,
380+
"database": "dbname",
381+
"sanitized_url": "postgresql://user:***@localhost:5432/dbname",
382+
},
383+
),
384+
# Username with null password
385+
(
386+
"postgresql://user@localhost/dbname",
387+
{
388+
"scheme": "postgresql",
389+
"username": "user",
390+
"password": None,
391+
"hostname": "localhost",
392+
"port": None,
393+
"database": "dbname",
394+
"sanitized_url": "postgresql://user@localhost/dbname",
395+
},
396+
),
397+
# Password is "0"
398+
(
399+
"postgresql://user:0@localhost/dbname",
400+
{
401+
"scheme": "postgresql",
402+
"username": "user",
403+
"password": "0",
404+
"hostname": "localhost",
405+
"port": None,
406+
"database": "dbname",
407+
"sanitized_url": "postgresql://user:***@localhost/dbname",
408+
},
409+
),
410+
# Empty password
411+
(
412+
"postgresql://user:@localhost/dbname",
413+
{
414+
"scheme": "postgresql",
415+
"username": "user",
416+
"password": "",
417+
"hostname": "localhost",
418+
"port": None,
419+
"database": "dbname",
420+
"sanitized_url": "postgresql://user@localhost/dbname",
421+
},
422+
),
423+
# No username
424+
(
425+
"postgresql://localhost:5432/dbname",
426+
{
427+
"scheme": "postgresql",
428+
"username": None,
429+
"password": None,
430+
"hostname": "localhost",
431+
"port": 5432,
432+
"database": "dbname",
433+
"sanitized_url": "postgresql://localhost:5432/dbname",
434+
},
435+
),
436+
# With query params
437+
(
438+
"postgresql://user:secret@localhost/dbname?query#fragment",
439+
{
440+
"scheme": "postgresql",
441+
"username": "user",
442+
"password": "secret",
443+
"hostname": "localhost",
444+
"port": None,
445+
"database": "dbname",
446+
"sanitized_url": "postgresql://user:***@localhost/dbname?query#fragment",
447+
},
448+
),
449+
],
450+
)
451+
async def test_parsed_url(raw_url, expected):
452+
parsed_result = ParseResult(urlparse(raw_url))
453+
454+
assert parsed_result.scheme == expected["scheme"]
455+
assert parsed_result.username == expected["username"]
456+
assert parsed_result.password == expected["password"]
457+
assert parsed_result.hostname == expected["hostname"]
458+
assert parsed_result.port == expected["port"]
459+
assert parsed_result.database == expected["database"]
460+
assert parsed_result.sanitized_url == expected["sanitized_url"]

0 commit comments

Comments
 (0)