Skip to content

Commit 8f9a34d

Browse files
fix: censoring sensitive values from being logged (#4818)
1 parent f4aa8d2 commit 8f9a34d

File tree

2 files changed

+103
-5
lines changed

2 files changed

+103
-5
lines changed

src/sagemaker/config/config_utils.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import logging
2121
import sys
2222
from typing import Callable
23+
import re
24+
from copy import deepcopy
2325

2426

2527
def get_sagemaker_config_logger():
@@ -67,6 +69,19 @@ def _log_sagemaker_config_single_substitution(source_value, config_value, config
6769
"""
6870
logger = get_sagemaker_config_logger()
6971

72+
source_value_log_copy = deepcopy(source_value)
73+
config_value_log_copy = deepcopy(config_value)
74+
75+
if isinstance(source_value_log_copy, dict):
76+
for key in source_value_log_copy.keys():
77+
if re.search(r"(secret|password|key|token)", key, re.IGNORECASE):
78+
source_value_log_copy[key] = "***"
79+
80+
if isinstance(config_value_log_copy, dict):
81+
for key in config_value_log_copy.keys():
82+
if re.search(r"(secret|password|key|token)", key, re.IGNORECASE):
83+
config_value_log_copy[key] = "***"
84+
7085
if config_value is not None:
7186

7287
if source_value is None:
@@ -79,7 +94,7 @@ def _log_sagemaker_config_single_substitution(source_value, config_value, config
7994
logger.debug(
8095
"Applied value\n config key = %s\n config value that will be used = %s",
8196
config_key_path,
82-
config_value,
97+
config_value_log_copy,
8398
)
8499
else:
85100
logger.info(
@@ -102,8 +117,8 @@ def _log_sagemaker_config_single_substitution(source_value, config_value, config
102117
" source value that will be used = %s"
103118
),
104119
config_key_path,
105-
config_value,
106-
source_value,
120+
config_value_log_copy,
121+
source_value_log_copy,
107122
)
108123
elif source_value is not None and config_value != source_value:
109124
# Sagemaker Config had a value defined that is NOT going to be used
@@ -117,8 +132,8 @@ def _log_sagemaker_config_single_substitution(source_value, config_value, config
117132
" source value that will be used = %s",
118133
),
119134
config_key_path,
120-
config_value,
121-
source_value,
135+
config_value_log_copy,
136+
source_value_log_copy,
122137
)
123138
else:
124139
# nothing was specified in the config and nothing is being automatically applied

tests/unit/test_utils.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import absolute_import
1616

1717
import copy
18+
import logging
1819
import shutil
1920
import tarfile
2021
from datetime import datetime
@@ -59,6 +60,7 @@
5960
_validate_new_tags,
6061
remove_tag_with_key,
6162
)
63+
from src.sagemaker.config.config_utils import _log_sagemaker_config_single_substitution
6264
from tests.unit.sagemaker.workflow.helpers import CustomStep
6365
from sagemaker.workflow.parameters import ParameterString, ParameterInteger
6466

@@ -1279,6 +1281,87 @@ def test_resolve_value_from_config():
12791281
mock_info_logger.reset_mock()
12801282

12811283

1284+
class TestLogSagemakerConfig(TestCase):
1285+
1286+
def test_sensitive_info_masking(self):
1287+
logger = logging.getLogger("sagemaker.config")
1288+
logger.setLevel(logging.DEBUG)
1289+
1290+
stream_handler = logging.StreamHandler()
1291+
logger.addHandler(stream_handler)
1292+
1293+
# source value is None
1294+
with self.assertLogs(logger, level="DEBUG") as log:
1295+
_log_sagemaker_config_single_substitution(
1296+
None, {"apiKey": "topsecretkey"}, "config/path"
1297+
)
1298+
1299+
self.assertIn("config value that will be used = {'apiKey': '***'}", log.output[0])
1300+
1301+
# source value is None and config_value == source_value
1302+
with self.assertLogs(logger, level="DEBUG") as log:
1303+
_log_sagemaker_config_single_substitution(
1304+
{"secretword": "topsecretword"}, {"secretword": "topsecretword"}, "config/path"
1305+
)
1306+
1307+
self.assertIn("Skipped value", log.output[0])
1308+
self.assertIn("source value that will be used = {'secretword': '***'}", log.output[0])
1309+
self.assertIn("config value = {'secretword': '***'}", log.output[0])
1310+
1311+
# source value is not None and config_value != source_value
1312+
with self.assertLogs(logger, level="DEBUG") as log:
1313+
_log_sagemaker_config_single_substitution(
1314+
{"password": "supersecretpassword"}, {"apiKey": "topsecretkey"}, "config/path"
1315+
)
1316+
1317+
self.assertIn("Skipped value", log.output[0])
1318+
self.assertIn("source value that will be used = {'password': '***'}", log.output[0])
1319+
self.assertIn("config value = {'apiKey': '***'}", log.output[0])
1320+
1321+
def test_non_sensitive_info_masking(self):
1322+
logger = logging.getLogger("sagemaker.config")
1323+
logger.setLevel(logging.DEBUG)
1324+
1325+
stream_handler = logging.StreamHandler()
1326+
logger.addHandler(stream_handler)
1327+
1328+
# source value is None
1329+
with self.assertLogs(logger, level="DEBUG") as log:
1330+
_log_sagemaker_config_single_substitution(
1331+
None, {"username": "randomvalue"}, "config/path"
1332+
)
1333+
1334+
self.assertIn("config value that will be used = {'username': 'randomvalue'}", log.output[0])
1335+
1336+
# source value is not None and config_value == source_value
1337+
with self.assertLogs(logger, level="DEBUG") as log:
1338+
_log_sagemaker_config_single_substitution(
1339+
{"nonsensitivevalue": "randomvalue"},
1340+
{"nonsensitivevalue": "randomvalue"},
1341+
"config/path",
1342+
)
1343+
1344+
self.assertIn("Skipped value", log.output[0])
1345+
self.assertIn(
1346+
"source value that will be used = {'nonsensitivevalue': 'randomvalue'}", log.output[0]
1347+
)
1348+
self.assertIn("config value = {'nonsensitivevalue': 'randomvalue'}", log.output[0])
1349+
1350+
# source value is not None and config_value != source_value
1351+
with self.assertLogs(logger, level="DEBUG") as log:
1352+
_log_sagemaker_config_single_substitution(
1353+
{"username": "nonsensitiveinfo"},
1354+
{"configvalue": "nonsensitivevalue"},
1355+
"config/path/non_sensitive",
1356+
)
1357+
1358+
self.assertIn("Skipped value", log.output[0])
1359+
self.assertIn(
1360+
"source value that will be used = {'username': 'nonsensitiveinfo'}", log.output[0]
1361+
)
1362+
self.assertIn("config value = {'configvalue': 'nonsensitivevalue'}", log.output[0])
1363+
1364+
12821365
def test_get_sagemaker_config_value():
12831366
mock_config_logger = Mock()
12841367

0 commit comments

Comments
 (0)