Skip to content

Commit

Permalink
Implement get_config sanitized
Browse files Browse the repository at this point in the history
Adds another keyword argument to the get_config method, in order to
filter out various sensitive details such as secrets, passwords, etc.

Also corrects the test for Junos which weren't testing anything
actually.
  • Loading branch information
mirceaulinic committed May 1, 2020
1 parent 9731790 commit 10b2c7a
Show file tree
Hide file tree
Showing 35 changed files with 2,328 additions and 128 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Expand Up @@ -303,6 +303,7 @@
"get_config_filtered": "get_config",
"get_arp_table_with_vrf": "get_arp_table",
"get_route_to_longer": "get_route_to",
"get_config_sanitized": "get_config",
}


Expand Down
3 changes: 2 additions & 1 deletion napalm/base/base.py
Expand Up @@ -1510,14 +1510,15 @@ def get_optics(self):
"""
raise NotImplementedError

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):
"""
Return the configuration of a device.
Args:
retrieve(string): Which configuration type you want to populate, default is all of them.
The rest will be set to "".
full(bool): Retrieve all the configuration. For instance, on ios, "sh run all".
sanitized(bool): Remove secret data. Default: ``False``.
Returns:
The object returned is a dictionary with a key for each configuration store:
Expand Down
21 changes: 21 additions & 0 deletions napalm/base/constants.py
Expand Up @@ -76,3 +76,24 @@
"c": "docsis-cable-device",
"s": "station",
}

CISCO_SANITIZE_FILTERS = {
r"^(snmp-server community).*$": r"\1 <removed>",
r"^(snmp-server host \S+( vrf \S+)?( version (1|2c|3))?)\s+\S+((\s+\S*)*)\s*$": r"\1 <removed> \5", # noqa
r"^(username .+ (password|secret) \d) .+$": r"\1 <removed>",
r"^(enable (password|secret)( level \d+)? \d) .+$": r"\1 <removed>",
r"^(\s+(?:password|secret)) (?:\d )?\S+$": r"\1 <removed>",
r"^(.*wpa-psk ascii \d) (\S+)$": r"\1 <removed>",
r"^(.*key 7) (\d.+)$": r"\1 <removed>",
r"^(tacacs-server (.+ )?key) .+$": r"\1 <removed>",
r"^(crypto isakmp key) (\S+) (.*)$": r"\1 <removed> \3",
r"^(\s+ip ospf message-digest-key \d+ md5) .+$": r"\1 <removed>",
r"^(\s+ip ospf authentication-key) .+$": r"\1 <removed>",
r"^(\s+neighbor \S+ password) .+$": r"\1 <removed>",
r"^(\s+vrrp \d+ authentication text) .+$": r"\1 <removed>",
r"^(\s+standby \d+ authentication) .{1,8}$": r"\1 <removed>",
r"^(\s+standby \d+ authentication md5 key-string) .+?( timeout \d+)?$": r"\1 <removed> \2",
r"^(\s+key-string) .+$": r"\1 <removed>",
r"^((tacacs|radius) server [^\n]+\n(\s+[^\n]+\n)*\s+key) [^\n]+$": r"\1 <removed>",
r"^(\s+ppp (chap|pap) password \d) .+$": r"\1 <removed>",
}
20 changes: 20 additions & 0 deletions napalm/base/helpers.py
Expand Up @@ -482,3 +482,23 @@ def generate_regex_or(filters):
return_pattern += rf"{pattern}|"
return_pattern += r")"
return return_pattern


def sanitize_config(config, filters):
"""
Given a list of filters, remove sensitive data from the provided config.
"""
for filter_, replace in filters.items():
config = re.sub(filter_, replace, config, flags=re.M)
return config


def sanitize_configs(configs, filters):
"""
Apply sanitize_config on the dictionary of configs typically returned by
the get_config method.
"""
for cfg_name, config in configs.items():
if config.strip():
configs[cfg_name] = sanitize_config(config, filters)
return configs
10 changes: 10 additions & 0 deletions napalm/base/test/getters.py
Expand Up @@ -521,6 +521,16 @@ def test_get_config_filtered(self, test_case):

return get_config

@wrap_test_cases
def test_get_config_sanitized(self, test_case):
"""Test get_config method."""
get_config = self.device.get_config(sanitized=True)

assert isinstance(get_config, dict)
assert helpers.test_model(models.config, get_config)

return get_config

@wrap_test_cases
def test_get_network_instances(self, test_case):
"""Test get_network_instances method."""
Expand Down
19 changes: 15 additions & 4 deletions napalm/eos/eos.py
Expand Up @@ -1750,7 +1750,7 @@ def get_optics(self):

return optics_detail

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):
"""get_config implementation for EOS."""
get_startup = retrieve == "all" or retrieve == "startup"
get_running = retrieve == "all" or retrieve == "running"
Expand All @@ -1760,18 +1760,29 @@ def get_config(self, retrieve="all", full=False):

# EOS only supports "all" on "show run"
run_full = " all" if full else ""
run_sanitized = " sanitized" if sanitized else ""

if retrieve == "all":
commands = ["show startup-config", "show running-config{}".format(run_full)]
commands = [
"show startup-config",
"show running-config{0}{1}".format(run_full, run_sanitized),
]

if self.config_session:
commands.append(
"show session-config named {}".format(self.config_session)
"show session-config named {0}{1}".format(
self.config_session, run_sanitized
)
)

output = self.device.run_commands(commands, encoding="text")
startup_cfg = str(output[0]["output"]) if get_startup else ""
if sanitized and startup_cfg:
startup_cfg = napalm.base.helpers.sanitize_config(
startup_cfg, c.CISCO_SANITIZE_FILTERS
)
return {
"startup": str(output[0]["output"]) if get_startup else "",
"startup": startup_cfg,
"running": str(output[1]["output"]) if get_running else "",
"candidate": str(output[2]["output"]) if get_candidate else "",
}
Expand Down
6 changes: 5 additions & 1 deletion napalm/ios/ios.py
Expand Up @@ -42,6 +42,7 @@
split_interface,
abbreviated_interface_name,
generate_regex_or,
sanitize_configs,
)
from napalm.base.netmiko_helpers import netmiko_args

Expand Down Expand Up @@ -3368,7 +3369,7 @@ def get_network_instances(self, name=""):
except AttributeError:
raise ValueError("The vrf %s does not exist" % name)

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):
"""Implementation of get_config for IOS.
Returns the startup or/and running configuration as dictionary.
Expand Down Expand Up @@ -3403,6 +3404,9 @@ def get_config(self, retrieve="all", full=False):
output = re.sub(filter_pattern, "", output, flags=re.M)
configs["running"] = output.strip()

if sanitized:
return sanitize_configs(configs, C.CISCO_SANITIZE_FILTERS)

return configs

def get_ipv6_neighbors_table(self):
Expand Down
7 changes: 6 additions & 1 deletion napalm/iosxr/iosxr.py
Expand Up @@ -2228,7 +2228,7 @@ def get_users(self):

return users

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):

config = {"startup": "", "running": "", "candidate": ""} # default values

Expand All @@ -2244,4 +2244,9 @@ def get_config(self, retrieve="all", full=False):
self.device._execute_config_show("show configuration merge")
)

if sanitized:
return napalm.base.helpers.sanitize_configs(
config, C.CISCO_SANITIZE_FILTERS
)

return config
11 changes: 9 additions & 2 deletions napalm/junos/junos.py
Expand Up @@ -2234,18 +2234,25 @@ def get_optics(self):

return optics_detail

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):
rv = {"startup": "", "running": "", "candidate": ""}

options = {"format": "text", "database": "candidate"}

sanitize_strings = {
r"^(\s+community\s+)\w+(\s+{.*)$": r"\1<removed>\2",
r'^(.*)"\$\d\$\S+"(;.*)$': r"\1<removed>\2",
}
if retrieve in ("candidate", "all"):
config = self.device.rpc.get_config(filter_xml=None, options=options)
rv["candidate"] = str(config.text)
if retrieve in ("running", "all"):
options["database"] = "committed"
config = self.device.rpc.get_config(filter_xml=None, options=options)
rv["running"] = str(config.text)

if sanitized:
return napalm.base.helpers.sanitize_configs(rv, sanitize_strings)

return rv

def get_network_instances(self, name=""):
Expand Down
8 changes: 7 additions & 1 deletion napalm/nxos/nxos.py
Expand Up @@ -515,7 +515,7 @@ def _create_tmp_file(config):
def _disable_confirmation(self):
self._send_command_list(["terminal dont-ask"])

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):

# NX-OS adds some extra, unneeded lines that should be filtered.
filter_strings = [
Expand All @@ -539,6 +539,12 @@ def get_config(self, retrieve="all", full=False):
output = self._send_command(command, raw_text=True)
output = re.sub(filter_pattern, "", output, flags=re.M)
config["startup"] = output.strip()

if sanitized:
return napalm.base.helpers.sanitize_configs(
config, c.CISCO_SANITIZE_FILTERS
)

return config

def get_lldp_neighbors(self):
Expand Down
40 changes: 40 additions & 0 deletions test/base/test_helpers.py
Expand Up @@ -6,6 +6,7 @@
import os
import sys
import unittest
import textwrap

# third party libs
try:
Expand Down Expand Up @@ -38,6 +39,7 @@

# NAPALM base
import napalm.base.helpers
import napalm.base.constants as C
from napalm.base.netmiko_helpers import netmiko_args
import napalm.base.exceptions
from napalm.base.base import NetworkDriver
Expand Down Expand Up @@ -633,6 +635,44 @@ def test_netmiko_arguments(self):
result_dict.pop("transport")
self.assertEqual(netmiko_args(test_case), result_dict)

def test_sanitized_config(self):
config = textwrap.dedent(
"""\
!
version 15.5
service timestamps debug datetime msec
service timestamps log datetime msec
no platform punt-keepalive disable-kernel-core
platform console auto
!
hostname CSR1
enable secret 5 $1$asdiud2982fp2f
username root password 7 096f471a1a0a57213e2f2f19
!
redundancy
radius-server host 10.124.24.18 auth-port 1812\
acct-port 1813 key 7 09687b2a3245343b382f2b"""
)
expected = textwrap.dedent(
"""\
!
version 15.5
service timestamps debug datetime msec
service timestamps log datetime msec
no platform punt-keepalive disable-kernel-core
platform console auto
!
hostname CSR1
enable secret 5 <removed>
username root password 7 <removed>
!
redundancy
radius-server host 10.124.24.18 auth-port 1812\
acct-port 1813 key 7 <removed>"""
)
ret = napalm.base.helpers.sanitize_config(config, C.CISCO_SANITIZE_FILTERS)
self.assertEqual(ret, expected)


class FakeNetworkDriver(NetworkDriver):
def __init__(self):
Expand Down
@@ -0,0 +1,5 @@
{
"startup": "! Command: show startup-config\n! Startup-config last modified at Sun Sep 11 18:35:01 2016 by root\n! device: localhost (vEOS, EOS-4.15.2.1F)\n!\n! boot system flash:/vEOS-lab.swi\n!\nevent-handler dhclient\n trigger on-boot\n action bash sudo /mnt/flash/initialize_ma1.sh\n !\n transceiver qsfp default-mode 4x10G\n !\n spanning-tree mode mstp\n !\n aaa authorization exec default local\n !\n aaa root secret 5 $1$zr7sHZaW$WagNykw1d5wjy4ZhtMgUS/\n !\n username admin privilege 15 role network-admin secret 5 $1$VoQBHXed$4z2.EoAeoIY2SFme/Pz3Q/\n username vagrant privilege 15 role network-admin secret 5 $1$l/6gq.Qs$ifURvKWUYzLm0RqmRgr.W1\n !\n interface Ethernet1\n !\n interface Ethernet2\n !\n interface Management1\n ip address 10.0.2.15/24\n\t !\n\t no ip routing\n\t !\n\t management api http-commands\n\t no shutdown\n\t !\n\t !\n\t end\n",
"running": "! Command: show startup-config\n! Startup-config last modified at Sun Sep 11 18:35:01 2016 by root\n! device: localhost (vEOS, EOS-4.15.2.1F)\n!\n! boot system flash:/vEOS-lab.swi\n!\nevent-handler dhclient\n trigger on-boot\n action bash sudo /mnt/flash/initialize_ma1.sh\n !\n transceiver qsfp default-mode 4x10G\n !\n spanning-tree mode mstp\n !\n aaa authorization exec default local\n !\n aaa root secret 5 <removed>\n !\n username admin privilege 15 role network-admin secret 5 <removed>\n username vagrant privilege 15 role network-admin secret 5 <removed>\n !\n interface Ethernet1\n !\n interface Ethernet2\n !\n interface Management1\n ip address 10.0.2.15/24\n\t !\n\t no ip routing\n\t !\n\t management api http-commands\n\t no shutdown\n\t !\n\t !\n\t end\n",
"candidate": ""
}
@@ -0,0 +1,35 @@
! Command: show startup-config
! Startup-config last modified at Sun Sep 11 18:35:01 2016 by root
! device: localhost (vEOS, EOS-4.15.2.1F)
!
! boot system flash:/vEOS-lab.swi
!
event-handler dhclient
trigger on-boot
action bash sudo /mnt/flash/initialize_ma1.sh
!
transceiver qsfp default-mode 4x10G
!
spanning-tree mode mstp
!
aaa authorization exec default local
!
aaa root secret 5 <removed>
!
username admin privilege 15 role network-admin secret 5 <removed>
username vagrant privilege 15 role network-admin secret 5 <removed>
!
interface Ethernet1
!
interface Ethernet2
!
interface Management1
ip address 10.0.2.15/24
!
no ip routing
!
management api http-commands
no shutdown
!
!
end
@@ -0,0 +1,35 @@
! Command: show startup-config
! Startup-config last modified at Sun Sep 11 18:35:01 2016 by root
! device: localhost (vEOS, EOS-4.15.2.1F)
!
! boot system flash:/vEOS-lab.swi
!
event-handler dhclient
trigger on-boot
action bash sudo /mnt/flash/initialize_ma1.sh
!
transceiver qsfp default-mode 4x10G
!
spanning-tree mode mstp
!
aaa authorization exec default local
!
aaa root secret 5 $1$zr7sHZaW$WagNykw1d5wjy4ZhtMgUS/
!
username admin privilege 15 role network-admin secret 5 $1$VoQBHXed$4z2.EoAeoIY2SFme/Pz3Q/
username vagrant privilege 15 role network-admin secret 5 $1$l/6gq.Qs$ifURvKWUYzLm0RqmRgr.W1
!
interface Ethernet1
!
interface Ethernet2
!
interface Management1
ip address 10.0.2.15/24
!
no ip routing
!
management api http-commands
no shutdown
!
!
end
@@ -0,0 +1,5 @@
{
"startup": "!\n\n!\nversion 15.5\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno platform punt-keepalive disable-kernel-core\nplatform console auto\n!\nhostname CSR1\n!\nboot-start-marker\nboot-end-marker\n!\n!\nenable password cisco\n!\naaa new-model\n!\n!\naaa authentication login default local\naaa authorization exec default local\n!\n!\n!\n!\n!\naaa session-id common\n!\nip vrf MGMT\n!\n!\n!\n!\n!\n!\n!\n!\n!\n\n\nip domain name example.local\n\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nsubscriber templating\n!\nmultilink bundle-name authenticated\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nlicense udi pid CSR1000V sn 9OSEGKJXRHE\nspanning-tree extend system-id\n!\nusername cisco privilege 15 password 0 <removed>\n!\nredundancy\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\ninterface Loopback0\n ip address 1.1.1.1 255.255.255.255\n!\ninterface GigabitEthernet1\n ip vrf forwarding MGMT\n ip address 192.168.35.121 255.255.255.0\n negotiation auto\n!\ninterface GigabitEthernet2\n ip address 10.1.1.1 255.255.255.0\n negotiation auto\n!\ninterface GigabitEthernet3\n no ip address\n shutdown\n negotiation auto\n!\nrouter ospf 1\n redistribute connected subnets\n network 10.1.1.0 0.0.0.255 area 0\n!\n!\nvirtual-service csr_mgmt\n!\nip forward-protocol nd\n!\nno ip http server\nno ip http secure-server\n!\n!\n!\n!\n!\n!\ncontrol-plane\n!\n !\n !\n !\n !\n!\n!\n!\n!\n!\nline con 0\nline vty 0 4\n!\n!\nend",
"running": "!\n\n!\nversion 15.5\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno platform punt-keepalive disable-kernel-core\nplatform console auto\n!\nhostname CSR1\n!\nboot-start-marker\nboot-end-marker\n!\n!\nenable password cisco\n!\naaa new-model\n!\n!\naaa authentication login default local\naaa authorization exec default local\n!\n!\n!\n!\n!\naaa session-id common\n!\nip vrf MGMT\n!\n!\n!\n!\n!\n!\n!\n!\n!\n\n\nip domain name example.local\n\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nsubscriber templating\n!\nmultilink bundle-name authenticated\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nlicense udi pid CSR1000V sn 9OSEGKJXRHE\nspanning-tree extend system-id\n!\nusername cisco privilege 15 password 0 <removed>\n!\nredundancy\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\ninterface Loopback0\n ip address 1.1.1.1 255.255.255.255\n!\ninterface GigabitEthernet1\n ip vrf forwarding MGMT\n ip address 192.168.35.121 255.255.255.0\n negotiation auto\n!\ninterface GigabitEthernet2\n ip address 10.1.1.1 255.255.255.0\n negotiation auto\n!\ninterface GigabitEthernet3\n no ip address\n shutdown\n negotiation auto\n!\nrouter ospf 1\n redistribute connected subnets\n network 10.1.1.0 0.0.0.255 area 0\n!\n!\nvirtual-service csr_mgmt\n!\nip forward-protocol nd\n!\nno ip http server\nno ip http secure-server\n!\n!\n!\n!\n!\n!\ncontrol-plane\n!\n !\n !\n !\n !\n!\n!\n!\n!\n!\nline con 0\nline vty 0 4\n!\n!\nend",
"candidate": ""
}

0 comments on commit 10b2c7a

Please sign in to comment.