Filter out details from taskflow logs with v2+jobboard
When enabling INFO-level logs in taskflow and using jobboard with amphorav2, taskflow prints the string representation of a job when it is completed. It includes the parameters of the flow, which might include private information from TLS-enabled listeners and pools such as certificates, private_key and intermediate certificates. This commit filters out the private information from the logs by using logging.Filter, it replaces private attributes with '***'. Story 2010523 Task 47125 Change-Id: I2df8a49851feb1445b5128ce99b880ddb77782ad
This commit is contained in:
parent
eea6ac62fd
commit
6c731fa2fd
@ -24,6 +24,7 @@ from oslo_utils import uuidutils
|
|||||||
from taskflow.conductors.backends import impl_blocking
|
from taskflow.conductors.backends import impl_blocking
|
||||||
from taskflow import engines
|
from taskflow import engines
|
||||||
from taskflow import exceptions as taskflow_exc
|
from taskflow import exceptions as taskflow_exc
|
||||||
|
from taskflow.jobs.base import Job
|
||||||
from taskflow.listeners import base
|
from taskflow.listeners import base
|
||||||
from taskflow.listeners import logging
|
from taskflow.listeners import logging
|
||||||
from taskflow.persistence import models
|
from taskflow.persistence import models
|
||||||
@ -51,6 +52,44 @@ def retryMaskFilter(record):
|
|||||||
LOG.logger.addFilter(retryMaskFilter)
|
LOG.logger.addFilter(retryMaskFilter)
|
||||||
|
|
||||||
|
|
||||||
|
def _details_filter(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
ret = {}
|
||||||
|
for key in obj:
|
||||||
|
if (key in ('certificate', 'private_key', 'passphrase') and
|
||||||
|
isinstance(obj[key], str)):
|
||||||
|
ret[key] = '***'
|
||||||
|
elif key == 'intermediates' and isinstance(obj[key], list):
|
||||||
|
ret[key] = ['***'] * len(obj[key])
|
||||||
|
else:
|
||||||
|
ret[key] = _details_filter(obj[key])
|
||||||
|
return ret
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [_details_filter(e) for e in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class FilteredJob(Job):
|
||||||
|
def __str__(self):
|
||||||
|
# Override the detault __str__ method from taskflow.job.base.Job,
|
||||||
|
# filter out private information from details
|
||||||
|
cls_name = type(self).__name__
|
||||||
|
details = _details_filter(self.details)
|
||||||
|
return "%s: %s (priority=%s, uuid=%s, details=%s)" % (
|
||||||
|
cls_name, self.name, self.priority,
|
||||||
|
self.uuid, details)
|
||||||
|
|
||||||
|
|
||||||
|
class JobDetailsFilter(log.logging.Filter):
|
||||||
|
def filter(self, record):
|
||||||
|
# If the first arg is a Job, convert it now to a string with our custom
|
||||||
|
# method
|
||||||
|
if isinstance(record.args[0], Job):
|
||||||
|
arg0 = record.args[0]
|
||||||
|
record.args = (FilteredJob.__str__(arg0),) + record.args[1:]
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class BaseTaskFlowEngine(object):
|
class BaseTaskFlowEngine(object):
|
||||||
"""This is the task flow engine
|
"""This is the task flow engine
|
||||||
|
|
||||||
@ -129,6 +168,11 @@ class TaskFlowServiceController(object):
|
|||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
|
# Install filter for taskflow executor logger
|
||||||
|
taskflow_logger = log.logging.getLogger(
|
||||||
|
"taskflow.conductors.backends.impl_executor")
|
||||||
|
taskflow_logger.addFilter(JobDetailsFilter())
|
||||||
|
|
||||||
def run_poster(self, flow_factory, *args, **kwargs):
|
def run_poster(self, flow_factory, *args, **kwargs):
|
||||||
with self.driver.persistence_driver.get_persistence() as persistence:
|
with self.driver.persistence_driver.get_persistence() as persistence:
|
||||||
with self.driver.job_board(persistence) as job_board:
|
with self.driver.job_board(persistence) as job_board:
|
||||||
|
@ -18,6 +18,7 @@ from unittest import mock
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as oslo_fixture
|
from oslo_config import fixture as oslo_fixture
|
||||||
from taskflow import engines as tf_engines
|
from taskflow import engines as tf_engines
|
||||||
|
from taskflow.jobs.base import Job
|
||||||
|
|
||||||
from octavia.common import base_taskflow
|
from octavia.common import base_taskflow
|
||||||
import octavia.tests.unit.base as base
|
import octavia.tests.unit.base as base
|
||||||
@ -173,3 +174,62 @@ class TestTaskFlowServiceController(base.TestCase):
|
|||||||
job1.extend_expiry.assert_called_once_with(30)
|
job1.extend_expiry.assert_called_once_with(30)
|
||||||
job2.extend_expiry.assert_not_called()
|
job2.extend_expiry.assert_not_called()
|
||||||
job3.extend_expiry.assert_not_called()
|
job3.extend_expiry.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
class TestJobDetailsFilter(base.TestCase):
|
||||||
|
def test_filter(self):
|
||||||
|
log_filter = base_taskflow.JobDetailsFilter()
|
||||||
|
|
||||||
|
tls_container_data = {
|
||||||
|
'certificate': '<CERTIFICATE>',
|
||||||
|
'private_key': '<PRIVATE_KEY>',
|
||||||
|
'passphrase': '<PASSPHRASE>',
|
||||||
|
'intermediates': [
|
||||||
|
'<INTERMEDIATE1>',
|
||||||
|
'<INTERMEDIATE2>'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
job = mock.Mock(spec=Job)
|
||||||
|
job.details = {
|
||||||
|
'store': {
|
||||||
|
'listeners': [
|
||||||
|
{
|
||||||
|
'name': 'listener_name',
|
||||||
|
'default_tls_container_data': tls_container_data
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'any_recursive': {
|
||||||
|
'type': [
|
||||||
|
{
|
||||||
|
'other_list': [
|
||||||
|
tls_container_data,
|
||||||
|
{
|
||||||
|
'test': tls_container_data,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record = mock.Mock()
|
||||||
|
record.args = (job, 'something')
|
||||||
|
|
||||||
|
ret = log_filter.filter(record)
|
||||||
|
self.assertTrue(ret)
|
||||||
|
|
||||||
|
self.assertNotIn(tls_container_data['certificate'], record.args[0])
|
||||||
|
self.assertNotIn(tls_container_data['private_key'], record.args[0])
|
||||||
|
self.assertNotIn(tls_container_data['passphrase'], record.args[0])
|
||||||
|
self.assertNotIn(tls_container_data['intermediates'][0],
|
||||||
|
record.args[0])
|
||||||
|
self.assertNotIn(tls_container_data['intermediates'][1],
|
||||||
|
record.args[0])
|
||||||
|
self.assertIn('listener_name', record.args[0])
|
||||||
|
|
||||||
|
record.args = ('arg1', 2)
|
||||||
|
|
||||||
|
ret = log_filter.filter(record)
|
||||||
|
self.assertTrue(ret)
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
security:
|
||||||
|
- |
|
||||||
|
Filter out private information from the taskflow logs when ''INFO'' level
|
||||||
|
messages are enabled and when jobboard is enabled. Logs might have included
|
||||||
|
TLS certificates and private_key. By default, in Octavia only WARNING and
|
||||||
|
above messages are enabled in taskflow and jobboard is disabled.
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
The parameters of a taskflow Flow were logged in ''INFO'' level messages by
|
||||||
|
taskflow, it included TLS-enabled listeners and pools parameters, such as
|
||||||
|
certificates and private_key.
|
Loading…
Reference in New Issue
Block a user