Skip to content

Commit

Permalink
NAS-130771 / 24.10-RC.1 / Add RedactedFileMetric and use to redact iS…
Browse files Browse the repository at this point in the history
…CSI CHAP secrets (by bmeagherix) (#212)

* Add RedactedFileMetric and use to redact iSCSI CHAP secrets

* Add test_redacted_file_metric

(cherry picked from commit 98828e4)

---------

Co-authored-by: Brian M <[email protected]>
  • Loading branch information
bugclerk and bmeagherix authored Aug 22, 2024
1 parent 0df5782 commit 9fa7a9c
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 4 deletions.
12 changes: 10 additions & 2 deletions ixdiagnose/plugins/iscsi.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import re

from ixdiagnose.utils.command import Command
from ixdiagnose.utils.formatter import remove_keys
from ixdiagnose.utils.middleware import MiddlewareCommand

from .base import Plugin
from .metrics import CommandMetric, FileMetric, MiddlewareClientMetric
from .metrics import CommandMetric, MiddlewareClientMetric, RedactedFileMetric
from .prerequisites import ServiceRunningPrerequisite

SENSITIVE_LINE = re.compile(r'^(\s*)(IncomingUser "|OutgoingUser ")(\S*) (\S*)"$')


def redact_chap_passwords(line):
return SENSITIVE_LINE.sub(r'\1\2\3 **REDACTED**"', line)


class ISCSI(Plugin):
name = 'iscsi'
Expand All @@ -30,7 +38,7 @@ class ISCSI(Plugin):
Command(['scstadmin', '-list_scst_attr'], 'Lists SCST core attributes', serializable=False),
], prerequisites=[ServiceRunningPrerequisite('scst')],
),
FileMetric('scst', '/etc/scst.conf', extension='.conf'),
RedactedFileMetric('scst', '/etc/scst.conf', extension='.conf', redact_callback=redact_chap_passwords),
]
raw_metrics = [
CommandMetric(
Expand Down
3 changes: 2 additions & 1 deletion ixdiagnose/plugins/metrics/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .base import Metric
from .command import CommandMetric
from .directory_tree import DirectoryTreeMetric
from .file import FileMetric
from .file import FileMetric, RedactedFileMetric
from .middleware import MiddlewareClientMetric
from .python import PythonMetric

Expand All @@ -13,4 +13,5 @@
'Metric',
'MiddlewareClientMetric',
'PythonMetric',
'RedactedFileMetric',
]
35 changes: 34 additions & 1 deletion ixdiagnose/plugins/metrics/file.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import shutil

from ixdiagnose.plugins.prerequisites.base import Prerequisite
from typing import Dict, List, Tuple
from typing import Callable, Dict, List, Tuple

from .base import Metric

Expand Down Expand Up @@ -30,3 +30,36 @@ def execute_impl(self) -> Tuple[Dict, str]:
report['error'] = f'{self.file_path!r} file path does not exist'

return report, output


class RedactedFileMetric(FileMetric):
"""
Copies the specified text file, calling the redact_callback on each line
to perform any necessary redaction.
"""

def __init__(self, name: str,
file_path: str,
prerequisites: List[Prerequisite] = None,
extension: str = '.txt',
redact_callback: Callable[[str], str] | None = None):
super().__init__(name, file_path, prerequisites, extension)
self.redact_callback = redact_callback

def execute_impl(self) -> Tuple[Dict, str]:
report = {
'error': None, 'description': f'Redacted contents of {self.file_path!r}',
}
output = ''
try:
with open(self.file_path, "r", encoding="utf-8") as input_file:
output_filepath = self.output_file_path(self.execution_context['output_dir'])
with open(output_filepath, "w", encoding="utf-8") as output_file:
for line in input_file:
if self.redact_callback:
line = self.redact_callback(line)
output_file.write(line)
except FileNotFoundError:
report['error'] = f'{self.file_path!r} file path does not exist'

return report, output
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
HANDLER vdisk_fileio {
}
HANDLER vdisk_blockio {
DEVICE test1 {
filename /dev/zvol/tank/test1
blocksize 512
read_only 0
usn cce1fa1b063f9d3
naa_id 0x6589cfc0000007ddc48032452639e00e
prod_id "iSCSI Disk"
rotational 0
t10_vend_id TrueNAS
t10_dev_id cce1fa1b063f9d3
threads_num 32
}

}

TARGET_DRIVER iscsi {
IncomingUser "User1 secpassword123"
enabled 1
link_local 0

TARGET iqn.2005-10.org.freenas.ctl:test1 {
rel_tgt_id 1
enabled 1
per_portal_acl 1
IncomingUser "User1 secpassword123"
OutgoingUser "User2 hellothere12"

GROUP security_group {
INITIATOR *\#1.2.3.4

LUN 0 test1
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
HANDLER vdisk_fileio {
}
HANDLER vdisk_blockio {
DEVICE test1 {
filename /dev/zvol/tank/test1
blocksize 512
read_only 0
usn cce1fa1b063f9d3
naa_id 0x6589cfc0000007ddc48032452639e00e
prod_id "iSCSI Disk"
rotational 0
t10_vend_id TrueNAS
t10_dev_id cce1fa1b063f9d3
threads_num 32
}

}

TARGET_DRIVER iscsi {
IncomingUser "User1 **REDACTED**"
enabled 1
link_local 0

TARGET iqn.2005-10.org.freenas.ctl:test1 {
rel_tgt_id 1
enabled 1
per_portal_acl 1
IncomingUser "User1 **REDACTED**"
OutgoingUser "User2 **REDACTED**"

GROUP security_group {
INITIATOR *\#1.2.3.4

LUN 0 test1
}
}
}

46 changes: 46 additions & 0 deletions ixdiagnose/test/pytest/unit/metrics/test_redacted_file_metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import contextlib
import filecmp
import os
import pytest

from ixdiagnose.plugins.metrics.file import RedactedFileMetric
from ixdiagnose.plugins.iscsi import redact_chap_passwords

from ixdiagnose.test.pytest.unit.utils import get_asset_path


TEST_FILE_DIR = '/tmp'


@pytest.mark.parametrize('name,raw_asset_filename,cooked_asset_filename,extension,callback', [
(
'scst.conf',
'redacted_file_metric_scst_input.txt',
'redacted_file_metric_scst_output.txt',
'.conf',
redact_chap_passwords
),
])
def test_redacted_file_metric(mocker,
monkeypatch,
name,
raw_asset_filename,
cooked_asset_filename,
extension,
callback):
raw_file_path = get_asset_path(raw_asset_filename)
cooked_file_path = get_asset_path(cooked_asset_filename)
output_file_path = os.path.join(TEST_FILE_DIR, name)

file_metric = RedactedFileMetric('scst', raw_file_path, extension=extension, redact_callback=callback)
file_metric.execution_context = {'output_dir': TEST_FILE_DIR}
assert file_metric.output_file_path(TEST_FILE_DIR) == output_file_path

try:
report = file_metric.execute_impl()[0]
assert os.path.exists(output_file_path) is True
assert report['error'] is None
assert filecmp.cmp(cooked_file_path, output_file_path, False) is True
finally:
with contextlib.suppress(FileNotFoundError):
os.unlink(output_file_path)
6 changes: 6 additions & 0 deletions ixdiagnose/test/pytest/unit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ def get_asset(filename):
caller_module_dir = os.path.dirname(os.path.abspath(caller_module.__file__))
with open(os.path.join(caller_module_dir, 'assets', filename), 'r') as f:
return f.read()


def get_asset_path(filename):
caller_module = inspect.getmodule(inspect.stack()[1][0])
caller_module_dir = os.path.dirname(os.path.abspath(caller_module.__file__))
return os.path.join(caller_module_dir, 'assets', filename)

0 comments on commit 9fa7a9c

Please sign in to comment.