Skip to content

Commit 56d2309

Browse files
authored
Fix session_log failure to hide "no_log" data. (#3331)
1 parent 5e6378a commit 56d2309

17 files changed

+465
-48
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ tests/etc/test_devices.yml
99
tests/etc/commands.yml
1010
tests/etc/responses.yml
1111
tests/etc/test_devices_exc.yml
12+
tests/SLOG/test_logging_filter_secrets-netmiko.log
1213

1314
examples/SECRET_DEVICE_CREDS.py
1415
./build

netmiko/base_connection.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ def wrapper_decorator(self: "BaseConnection", *args: Any, **kwargs: Any) -> Any:
102102
return cast(F, wrapper_decorator)
103103

104104

105+
def flush_session_log(func: F) -> F:
106+
@functools.wraps(func)
107+
def wrapper_decorator(self: "BaseConnection", *args: Any, **kwargs: Any) -> Any:
108+
try:
109+
return_val = func(self, *args, **kwargs)
110+
finally:
111+
# Always flush the session_log
112+
if self.session_log:
113+
self.session_log.flush()
114+
return return_val
115+
116+
return cast(F, wrapper_decorator)
117+
118+
105119
def log_writes(func: F) -> F:
106120
"""Handle both session_log and log of writes."""
107121

@@ -391,6 +405,7 @@ def __init__(
391405
elif isinstance(session_log, SessionLog):
392406
# SessionLog object
393407
self.session_log = session_log
408+
self.session_log.open()
394409
else:
395410
raise ValueError(
396411
"session_log must be a path to a file, a file handle, "
@@ -1467,6 +1482,7 @@ def command_echo_read(self, cmd: str, read_timeout: float) -> str:
14671482
pass
14681483
return new_data
14691484

1485+
@flush_session_log
14701486
@select_cmd_verify
14711487
def send_command_timing(
14721488
self,
@@ -1615,6 +1631,7 @@ def _prompt_handler(self, auto_find_prompt: bool) -> str:
16151631
prompt = self.base_prompt
16161632
return re.escape(prompt.strip())
16171633

1634+
@flush_session_log
16181635
@select_cmd_verify
16191636
def send_command(
16201637
self,
@@ -2138,6 +2155,7 @@ def send_config_from_file(
21382155
commands = cfg_file.readlines()
21392156
return self.send_config_set(commands, **kwargs)
21402157

2158+
@flush_session_log
21412159
def send_config_set(
21422160
self,
21432161
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,

netmiko/session_log.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def __init__(
1212
file_encoding: str = "utf-8",
1313
no_log: Optional[Dict[str, Any]] = None,
1414
record_writes: bool = False,
15+
slog_buffer: Optional[io.StringIO] = None,
1516
) -> None:
1617
if no_log is None:
1718
self.no_log = {}
@@ -30,6 +31,13 @@ def __init__(
3031
else:
3132
self.session_log = None
3233

34+
# In order to ensure all the no_log entries get hidden properly,
35+
# we must first store everying in memory and then write out to file.
36+
# Otherwise, we might miss the data we are supposed to hide (since
37+
# the no_log data potentially spans multiple reads).
38+
if slog_buffer is None:
39+
self.slog_buffer = io.StringIO()
40+
3341
# Ensures last write operations prior to disconnect are recorded.
3442
self.fin = False
3543

@@ -49,15 +57,30 @@ def open(self) -> None:
4957

5058
def close(self) -> None:
5159
"""Close the session_log file (if it is a file that we opened)."""
60+
self.flush()
5261
if self.session_log and self._session_log_close:
5362
self.session_log.close()
5463
self.session_log = None
5564

56-
def write(self, data: str) -> None:
57-
if self.session_log is not None and len(data) > 0:
58-
# Hide the password and secret in the session_log
59-
for hidden_data in self.no_log.values():
60-
data = data.replace(hidden_data, "********")
65+
def no_log_filter(self, data: str) -> str:
66+
"""Filter content from the session_log."""
67+
for hidden_data in self.no_log.values():
68+
data = data.replace(hidden_data, "********")
69+
return data
70+
71+
def _read_buffer(self) -> str:
72+
self.slog_buffer.seek(0)
73+
data = self.slog_buffer.read()
74+
# Once read, create a new buffer
75+
self.slog_buffer = io.StringIO()
76+
return data
77+
78+
def flush(self) -> None:
79+
"""Force the slog_buffer to be written out to the actual file"""
80+
81+
if self.session_log is not None:
82+
data = self._read_buffer()
83+
data = self.no_log_filter(data)
6184

6285
if isinstance(self.session_log, io.BufferedIOBase):
6386
self.session_log.write(write_bytes(data, encoding=self.file_encoding))
@@ -67,4 +90,10 @@ def write(self, data: str) -> None:
6790
assert isinstance(self.session_log, io.BufferedIOBase) or isinstance(
6891
self.session_log, io.TextIOBase
6992
)
93+
94+
# Flush the underlying file
7095
self.session_log.flush()
96+
97+
def write(self, data: str) -> None:
98+
if len(data) > 0:
99+
self.slog_buffer.write(data)

tests/SLOG/cisco881_slog.log

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
cisco1>
32
cisco1>terminal width 511
43
cisco1>terminal length 0
Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Initial file contents
22

3+
34
cisco1>
45
cisco1>terminal width 511
56
cisco1>terminal length 0
@@ -15,19 +16,3 @@ FastEthernet4 10.220.88.20 YES NVRAM up up
1516
Vlan1 unassigned YES unset down down
1617
cisco1>
1718
cisco1>exit
18-
cisco1>
19-
cisco1>terminal width 511
20-
cisco1>terminal length 0
21-
cisco1>
22-
cisco1>
23-
Testing password and secret replacement
24-
This is my password ********
25-
This is my secret ********
26-
27-
cisco1>
28-
cisco1>terminal width 511
29-
cisco1>terminal length 0
30-
cisco1>
31-
cisco1>
32-
Testing unicode
33-
😁😁

tests/SLOG/cisco881_slog_wr.log

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
terminal width 511
3+
34
cisco1>
45
cisco1>terminal width 511
56
cisco1>terminal length 0
@@ -8,12 +9,22 @@ cisco1>
89

910
cisco1>
1011

11-
cisco1>enable
12-
enable
13-
Password: ********
12+
cisco1>show foooooooo
13+
show foooooooo
14+
^
15+
% Invalid input detected at '^' marker.
1416

15-
cisco1#
17+
cisco1>
1618

17-
cisco1#
19+
cisco1>show ip interface brief
20+
show ip interface brief
21+
Interface IP-Address OK? Method Status Protocol
22+
FastEthernet0 unassigned YES unset down down
23+
FastEthernet1 unassigned YES unset down down
24+
FastEthernet2 unassigned YES unset down down
25+
FastEthernet3 unassigned YES unset down down
26+
FastEthernet4 10.220.88.20 YES NVRAM up up
27+
Vlan1 unassigned YES unset down down
28+
cisco1>
1829

19-
cisco1#exit
30+
cisco1>exit

tests/SLOG/netmiko.log

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ write_channel: b'terminal width 511\n'
3838
read_channel:
3939
cisco1>
4040
cisco1>
41-
read_channel: terminal widt
42-
read_channel: h 511
41+
read_channel: terminal wid
42+
read_channel: th 511
4343
cisco1>
4444
Pattern found: (terminal width 511)
4545
cisco1>
@@ -64,6 +64,16 @@ write_channel: b'\n'
6464
read_channel:
6565
read_channel:
6666
cisco1>
67+
68+
Parenthesis found in pattern.
69+
70+
pattern: (\#|>)
71+
72+
73+
This can be problemtic when used in read_until_pattern().
74+
75+
You should ensure that you use either non-capture groups i.e. '(?:' or that the
76+
parenthesis completely wrap the pattern '(pattern)'
6777
Pattern found: (\#|>)
6878
cisco1>
6979
read_channel:
@@ -72,8 +82,8 @@ write_channel: b'\n'
7282
write_channel: b'terminal width 511\n'
7383
read_channel: cisco1>
7484
cisco1>
75-
read_channel: terminal wid
76-
read_channel: th 511
85+
read_channel: terminal widt
86+
read_channel: h 511
7787
cisco1>
7888
Pattern found: (terminal width 511) cisco1>
7989
cisco1>terminal width 511
@@ -97,6 +107,16 @@ write_channel: b'\n'
97107
read_channel:
98108
read_channel:
99109
cisco1>
110+
111+
Parenthesis found in pattern.
112+
113+
pattern: (\#|>)
114+
115+
116+
This can be problemtic when used in read_until_pattern().
117+
118+
You should ensure that you use either non-capture groups i.e. '(?:' or that the
119+
parenthesis completely wrap the pattern '(pattern)'
100120
Pattern found: (\#|>)
101121
cisco1>
102122
read_channel:
@@ -110,8 +130,8 @@ read_channel:
110130
[find_prompt()]: prompt is cisco1>
111131
write_channel: b'show ip interface brief\n'
112132
read_channel:
113-
read_channel: show ip inter
114-
read_channel: face brief
133+
read_channel: show ip interf
134+
read_channel: ace brief
115135
Interface IP-Address OK? Method Status Protocol
116136
FastEthernet0 unassigned YES unset down down
117137
FastEthernet1 unassigned YES unset down down
@@ -129,3 +149,48 @@ cisco1>
129149
Pattern found: ([>#])
130150
cisco1>
131151
write_channel: b'exit\n'
152+
write_channel: b'\n'
153+
write_channel: b'terminal width 511\n'
154+
read_channel:
155+
cisco1>
156+
cisco1>
157+
read_channel: terminal wid
158+
read_channel: th 511
159+
cisco1>
160+
Pattern found: (terminal width 511)
161+
cisco1>
162+
cisco1>terminal width 511
163+
In disable_paging
164+
Command: terminal length 0
165+
166+
write_channel: b'terminal length 0\n'
167+
read_channel:
168+
read_channel: terminal lengt
169+
read_channel: h 0
170+
cisco1>
171+
Pattern found: (terminal\ length\ 0)
172+
cisco1>terminal length 0
173+
174+
cisco1>terminal length 0
175+
Exiting disable_paging
176+
read_channel:
177+
Clear buffer detects data in the channel
178+
read_channel:
179+
write_channel: b'\n'
180+
read_channel:
181+
read_channel:
182+
cisco1>
183+
184+
Parenthesis found in pattern.
185+
186+
pattern: (\#|>)
187+
188+
189+
This can be problemtic when used in read_until_pattern().
190+
191+
You should ensure that you use either non-capture groups i.e. '(?:' or that the
192+
parenthesis completely wrap the pattern '(pattern)'
193+
Pattern found: (\#|>)
194+
cisco1>
195+
read_channel:
196+
[find_prompt()]: prompt is cisco1>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Initial file contents
2+
3+
cisco1>
4+
cisco1>terminal width 511
5+
cisco1>terminal length 0
6+
cisco1>
7+
cisco1>
8+
cisco1>show ip interface brief
9+
Interface IP-Address OK? Method Status Protocol
10+
FastEthernet0 unassigned YES unset down down
11+
FastEthernet1 unassigned YES unset down down
12+
FastEthernet2 unassigned YES unset down down
13+
FastEthernet3 unassigned YES unset down down
14+
FastEthernet4 10.220.88.20 YES NVRAM up up
15+
Vlan1 unassigned YES unset down down
16+
cisco1>
17+
cisco1>exit
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
cisco1>
2+
cisco1>terminal width 511
3+
cisco1>terminal length 0
4+
cisco1>
5+
cisco1>
6+
cisco1>show ip interface brief
7+
Interface IP-Address OK? Method Status Protocol
8+
FastEthernet0 unassigned YES unset down down
9+
FastEthernet1 unassigned YES unset down down
10+
FastEthernet2 unassigned YES unset down down
11+
FastEthernet3 unassigned YES unset down down
12+
FastEthernet4 10.220.88.20 YES NVRAM up up
13+
Vlan1 unassigned YES unset down down
14+
cisco1>
15+
cisco1>exit
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
terminal width 511
3+
cisco1>
4+
cisco1>terminal width 511
5+
cisco1>terminal length 0
6+
terminal length 0
7+
cisco1>
8+
9+
cisco1>
10+
11+
cisco1>show ip interface brief
12+
show ip interface brief
13+
Interface IP-Address OK? Method Status Protocol
14+
FastEthernet0 unassigned YES unset down down
15+
FastEthernet1 unassigned YES unset down down
16+
FastEthernet2 unassigned YES unset down down
17+
FastEthernet3 unassigned YES unset down down
18+
FastEthernet4 10.220.88.20 YES NVRAM up up
19+
Vlan1 unassigned YES unset down down
20+
cisco1>
21+
22+
cisco1>exit

0 commit comments

Comments
 (0)