Skip to content

Commit a5c4530

Browse files
committed
Improved checking for sensitive properties in logs
The following sensitive data is now blanked out that was not blanked out before: In the HTTP operation logging: - "Create SSO Server Definition" operation: - `client-secret` request field - OIDC client secret for the SSO server In the API call logging: - SSOServerDefinitionManager.create(): - `client-secret` property - OIDC client secret for the SSO server Note that the support for SSO server definitions is new and has not yet been released. So this is not an externally documented fix. Details: * Changed the approach for blanking out sensitive properties in logs for API calls and return values: Instead of using the blanked_properties argument of the logged_api_call decorator to point to specific properties, the blanking out is now done when the string representation of the argument or return value has been created, by matching certain property names using a regexp in the repr() string of the arguments or return values. The blanked_properties argument in the logged_api_call decorator still exists in case it is needed in the future for additional properties, but it is not currently used. * Changed the approach for blanking out sensitive properties in logs for HTTP requests and responses: Instead of checking for a set of specific sensitive property names in the dict representation of the request and response bodies, the sensitive properties are now detected by matching certain property names using a regexp in the JSON string of the body. * In the test_logging.py unit test module, changed the use of .msg for accessing the expanded log message to using .getMessage(). It is not clear why that worked before, but getMessage() is the official way to get at the expanded log message. * Improved performance in _log_http_request() and _log_http_response() by not executing the code when debug logging is disabled. Signed-off-by: Andreas Maier <maiera@de.ibm.com>
1 parent 72d1386 commit a5c4530

12 files changed

Lines changed: 151 additions & 138 deletions

changes/noissue.24.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improved checking for sensitive properties in logs to reduce maintenance
2+
effort.

tests/unit/zhmcclient/test_logging.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def assert_log_capture(
162162
enter_record = log_capture.records[0]
163163
assert enter_record.name == _EXP_LOGGER_NAME
164164
assert enter_record.levelname == _EXP_LOG_LEVEL
165-
assert re.match(_EXP_LOG_MSG_ENTER_PATTERN, enter_record.msg)
165+
assert re.match(_EXP_LOG_MSG_ENTER_PATTERN, enter_record.getMessage())
166166
func_str, args_str, kwargs_str = enter_record.args
167167
if func_pattern:
168168
assert re.search(func_pattern, func_str)
@@ -174,7 +174,7 @@ def assert_log_capture(
174174
leave_record = log_capture.records[1]
175175
assert leave_record.name == _EXP_LOGGER_NAME
176176
assert leave_record.levelname == _EXP_LOG_LEVEL
177-
assert re.match(_EXP_LOG_MSG_LEAVE_PATTERN, leave_record.msg)
177+
assert re.match(_EXP_LOG_MSG_LEAVE_PATTERN, leave_record.getMessage())
178178
func_str, return_str = leave_record.args
179179
if func_pattern:
180180
assert re.search(func_pattern, func_str)

zhmcclient/_activation_profile.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,7 @@ def list(self, full_properties=False, filter_args=None,
225225
list_uri, result_prop, full_properties, filter_args,
226226
additional_properties)
227227

228-
@logged_api_call(blanked_properties=['ssc-master-pw', 'zaware-master-pw'],
229-
properties_pos=1)
228+
@logged_api_call
230229
def create(self, properties):
231230
"""
232231
Create and configure an Activation Profiles on this CPC, of the profile
@@ -346,8 +345,7 @@ def delete(self):
346345
self.get_properties_local(self.manager._name_prop, None))
347346
self.cease_existence_local()
348347

349-
@logged_api_call(blanked_properties=['ssc-master-pw', 'zaware-master-pw'],
350-
properties_pos=1)
348+
@logged_api_call
351349
def update_properties(self, properties):
352350
"""
353351
Update writeable properties of this Activation Profile.

zhmcclient/_constants.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
``zhmcclient`` namespace and should be used from there.
2424
"""
2525

26+
import re
27+
2628
__all__ = ['DEFAULT_CONNECT_TIMEOUT',
2729
'DEFAULT_CONNECT_RETRIES',
2830
'DEFAULT_HMC_PORT',
@@ -52,7 +54,9 @@
5254
'HTML_REASON_OTHER',
5355
'STOMP_MIN_CONNECTION_CHECK_TIME',
5456
'DEFAULT_WS_TIMEOUT',
55-
'BLANKED_OUT_STRING']
57+
'BLANKED_OUT_STRING',
58+
'BLANKED_OUT_PROPERTY_PATTERN',
59+
'BLANKED_OUT_PROPERTY_REPLACE']
5660

5761

5862
#: Default HMC port number
@@ -210,3 +214,14 @@
210214
#: Replacement string for blanked out sensitive values in log entries, such as
211215
#: passwords or session tokens.
212216
BLANKED_OUT_STRING = '********'
217+
218+
#: Regexp pattern for properties in the JSON or dict representation string that
219+
#: contains sensitive data and will be blanked out in any logs.
220+
BLANKED_OUT_PROPERTY_PATTERN = re.compile(
221+
r"""(['"])"""
222+
r"""([^'"]*(authentication-code|credential|key|passcode|password|pw"""
223+
r"""|secret|session))"""
224+
r"""\1\s*:\s*('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|None|null)""")
225+
226+
#: The replacement string corresponding to the regexp pattern.
227+
BLANKED_OUT_PROPERTY_REPLACE = rf"\1\2\1: \1{BLANKED_OUT_STRING}\1"

zhmcclient/_ldap_server_definition.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def list(self, full_properties=False, filter_args=None):
152152
return self._list_with_operation(
153153
list_uri, result_prop, full_properties, filter_args, None)
154154

155-
@logged_api_call(blanked_properties=['bind-password'], properties_pos=1)
155+
@logged_api_call
156156
def create(self, properties):
157157
"""
158158
Create a new LDAP Server Definition in this HMC.
@@ -258,7 +258,7 @@ def delete(self):
258258
self.get_properties_local(self.manager._name_prop, None))
259259
self.cease_existence_local()
260260

261-
@logged_api_call(blanked_properties=['bind-password'], properties_pos=1)
261+
@logged_api_call
262262
def update_properties(self, properties):
263263
"""
264264
Update writeable properties of this LDAP Server Definitions.

zhmcclient/_logging.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@
7373
logging.basicConfig(format=format_string, level=logging.DEBUG)
7474
"""
7575

76+
import re
7677
import logging
7778
import inspect
7879
import functools
7980
from collections.abc import Mapping, Sequence
8081

81-
from ._constants import API_LOGGER_NAME, BLANKED_OUT_STRING
82+
from ._constants import API_LOGGER_NAME, BLANKED_OUT_STRING, \
83+
BLANKED_OUT_PROPERTY_PATTERN, BLANKED_OUT_PROPERTY_REPLACE
8284

8385
__all__ = []
8486

@@ -115,6 +117,8 @@ def logged_api_call(
115117
The logger's name is the dotted module name of the module defining the
116118
decorated function (e.g. 'zhmcclient._cpc').
117119
120+
The values of sensitive properties will be blanked out.
121+
118122
Parameters:
119123
120124
org_func (function object): The original function being decorated.
@@ -277,22 +281,43 @@ def blanked_args(args, kwargs):
277281
blanked_dict(args[properties_pos])
278282
return tuple(logged_args), logged_kwargs
279283

284+
def log_repr(obj, name, trunc):
285+
"""
286+
Return a Python repr() representation of the object, with
287+
sensitive properties blanked out.
288+
"""
289+
obj_str = log_escaped(repr(obj))
290+
obj_str = re.sub(BLANKED_OUT_PROPERTY_PATTERN,
291+
BLANKED_OUT_PROPERTY_REPLACE, obj_str)
292+
obj_len = len(obj_str)
293+
if obj_len > trunc > 0:
294+
obj_str = (f"{name} (first {trunc} B of {obj_len} B): "
295+
f"{obj_str[0:trunc]} ...(truncated)")
296+
else:
297+
obj_str = f"{name}: {obj_str}"
298+
return obj_str
299+
280300
def log_call(args, kwargs):
281301
"""
282302
Log the call to the API function.
283303
"""
284-
logged_args, logged_kwargs = blanked_args(args, kwargs)
285-
logger.debug("Called: %s, args: %.500s, kwargs: %.500s",
286-
apifunc_str,
287-
log_escaped(repr(logged_args)),
288-
log_escaped(repr(logged_kwargs)))
304+
trunc = 500
305+
# We are calling log_trunc() also on args, because their values
306+
# (in theory) may contain dicts with sensitive properties.
307+
_args, _kwargs = blanked_args(args, kwargs)
308+
args_str = log_repr(_args, "args", trunc)
309+
kwargs_str = log_repr(_kwargs, "kwargs", trunc)
310+
logger.debug("Called: %s, %s, %s",
311+
apifunc_str, args_str, kwargs_str)
289312

290313
def log_return(result):
291314
"""
292315
Log the return from the API function.
293316
"""
294-
logger.debug("Return: %s, result: %.1000s",
295-
apifunc_str, log_escaped(repr(result)))
317+
trunc = 1000
318+
result_str = log_repr(result, "result", trunc)
319+
logger.debug("Return: %s, %s",
320+
apifunc_str, result_str)
296321

297322
@functools.wraps(func)
298323
def log_api_call(*args, **kwargs):

zhmcclient/_lpar.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,7 @@ def __init__(self, manager, uri, name=None, properties=None):
188188
f"got {type(manager)}")
189189
super().__init__(manager, uri, name, properties)
190190

191-
@logged_api_call(blanked_properties=['ssc-master-pw', 'zaware-master-pw'],
192-
properties_pos=1)
191+
@logged_api_call
193192
def update_properties(self, properties):
194193
"""
195194
Update writeable properties of this LPAR.

zhmcclient/_mfa_server_definition.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def list(self, full_properties=False, filter_args=None):
152152
return self._list_with_operation(
153153
list_uri, result_prop, full_properties, filter_args, None)
154154

155-
@logged_api_call(blanked_properties=['bind-password'], properties_pos=1)
155+
@logged_api_call
156156
def create(self, properties):
157157
"""
158158
Create a new MFA Server Definition in this HMC.
@@ -258,7 +258,7 @@ def delete(self):
258258
self.get_properties_local(self.manager._name_prop, None))
259259
self.cease_existence_local()
260260

261-
@logged_api_call(blanked_properties=['bind-password'], properties_pos=1)
261+
@logged_api_call
262262
def update_properties(self, properties):
263263
"""
264264
Update writeable properties of this MFA Server Definitions.

zhmcclient/_partition.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ def list(self, full_properties=False, filter_args=None,
178178
list_uri, result_prop, full_properties, filter_args,
179179
additional_properties)
180180

181-
@logged_api_call(blanked_properties=['boot-ftp-password', 'ssc-master-pw'],
182-
properties_pos=1)
181+
@logged_api_call
183182
def create(self, properties):
184183
"""
185184
Create and configure a Partition in this CPC.
@@ -703,8 +702,7 @@ def delete(self):
703702
self.get_properties_local(self.manager._name_prop, None))
704703
self.cease_existence_local()
705704

706-
@logged_api_call(blanked_properties=['boot-ftp-password', 'ssc-master-pw'],
707-
properties_pos=1)
705+
@logged_api_call
708706
def update_properties(self, properties):
709707
"""
710708
Update writeable properties of this Partition.

0 commit comments

Comments
 (0)