summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@baturin.org>2024-07-01 12:56:25 +0100
committerDaniil Baturin <daniil@baturin.org>2024-07-03 13:00:28 +0100
commit7958c9794813f399a2e113896410c0c03d29663d (patch)
tree6375caded54c44aa0ddbf0ce7927dfc3faef2ec0 /python
parente270712f7ebd76e4e1be598766d999cef4f05e26 (diff)
downloadvyos-1x-7958c9794813f399a2e113896410c0c03d29663d.tar.gz
vyos-1x-7958c9794813f399a2e113896410c0c03d29663d.zip
op-mode: T6498: add machine-readable tech support report script
Diffstat (limited to 'python')
-rw-r--r--python/vyos/utils/strip_config.py211
1 files changed, 211 insertions, 0 deletions
diff --git a/python/vyos/utils/strip_config.py b/python/vyos/utils/strip_config.py
new file mode 100644
index 000000000..32f076564
--- /dev/null
+++ b/python/vyos/utils/strip_config.py
@@ -0,0 +1,211 @@
+#!/usr/bin/python3
+#
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# XXX: these functions assume that the config is at the top level,
+# and aren't capable of anonymizing config subtress.
+# They shouldn't be used as a basis for a strip-private filter
+# until we figure out if we can pass the config path information to the filter.
+
+import sys
+import copy
+
+import vyos.configtree
+
+
+def __anonymize_password(v):
+ return "<PASSWORD REDACTED>"
+
+def __anonymize_key(v):
+ return "<KEY DATA REDACTED>"
+
+def __anonymize_data(v):
+ return "<DATA REDACTED>"
+
+__secret_paths = [
+ # System user password hashes
+ {"base_path": ['system', 'login', 'user'], "secret_path": ["authentication", "encrypted-password"], "func": __anonymize_password},
+
+ # PKI data
+ {"base_path": ["pki", "ca"], "secret_path": ["private", "key"], "func": __anonymize_key},
+ {"base_path": ["pki", "ca"], "secret_path": ["certificate"], "func": __anonymize_key},
+ {"base_path": ["pki", "ca"], "secret_path": ["crl"], "func": __anonymize_key},
+ {"base_path": ["pki", "certificate"], "secret_path": ["private", "key"], "func": __anonymize_key},
+ {"base_path": ["pki", "certificate"], "secret_path": ["certificate"], "func": __anonymize_key},
+ {"base_path": ["pki", "certificate"], "secret_path": ["acme", "email"], "func": __anonymize_data},
+ {"base_path": ["pki", "key-pair"], "secret_path": ["private", "key"], "func": __anonymize_key},
+ {"base_path": ["pki", "key-pair"], "secret_path": ["public", "key"], "func": __anonymize_key},
+ {"base_path": ["pki", "openssh"], "secret_path": ["private", "key"], "func": __anonymize_key},
+ {"base_path": ["pki", "openssh"], "secret_path": ["public", "key"], "func": __anonymize_key},
+ {"base_path": ["pki", "openvpn", "shared-secret"], "secret_path": ["key"], "func": __anonymize_key},
+ {"base_path": ["pki", "dh"], "secret_path": ["parameters"], "func": __anonymize_key},
+
+ # IPsec pre-shared secrets
+ {"base_path": ['vpn', 'ipsec', 'authentication', 'psk'], "secret_path": ["secret"], "func": __anonymize_password},
+
+ # IPsec x509 passphrases
+ {"base_path": ['vpn', 'ipsec', 'site-to-site', 'peer'], "secret_path": ['authentication', 'x509'], "func": __anonymize_password},
+
+ # IPsec remote-access secrets and passwords
+ {"base_path": ["vpn", "ipsec", "remote-access", "connection"], "secret_path": ["authentication", "pre-shared-secret"], "func": __anonymize_password},
+ # Passwords in remote-access IPsec local users have their own fixup
+ # due to deeper nesting.
+
+ # PPTP passwords
+ {"base_path": ['vpn', 'pptp', 'remote-access', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password},
+
+ # L2TP passwords
+ {"base_path": ['vpn', 'l2tp', 'remote-access', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password},
+ {"path": ['vpn', 'l2tp', 'remote-access', 'ipsec-settings', 'authentication', 'pre-shared-secret'], "func": __anonymize_password},
+
+ # SSTP passwords
+ {"base_path": ['vpn', 'sstp', 'remote-access', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password},
+
+ # OpenConnect passwords
+ {"base_path": ['vpn', 'openconnect', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password},
+
+ # PPPoE server passwords
+ {"base_path": ['service', 'pppoe-server', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password},
+
+ # RADIUS PSKs for VPN services
+ {"base_path": ["vpn", "sstp", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password},
+ {"base_path": ["vpn", "l2tp", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password},
+ {"base_path": ["vpn", "pptp", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password},
+ {"base_path": ["vpn", "openconnect", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password},
+ {"base_path": ["service", "ipoe-server", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password},
+ {"base_path": ["service", "pppoe-server", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password},
+
+ # VRRP passwords
+ {"base_path": ['high-availability', 'vrrp', 'group'], "secret_path": ['authentication', 'password'], "func": __anonymize_password},
+
+ # BGP neighbor and peer group passwords
+ {"base_path": ['protocols', 'bgp', 'neighbor'], "secret_path": ["password"], "func": __anonymize_password},
+ {"base_path": ['protocols', 'bgp', 'peer-group'], "secret_path": ["password"], "func": __anonymize_password},
+
+ # WireGuard private keys
+ {"base_path": ["interfaces", "wireguard"], "secret_path": ["private-key"], "func": __anonymize_password},
+
+ # NHRP passwords
+ {"base_path": ["protocols", "nhrp", "tunnel"], "secret_path": ["cisco-authentication"], "func": __anonymize_password},
+
+ # RIP passwords
+ {"base_path": ["protocols", "rip", "interface"], "secret_path": ["authentication", "plaintext-password"], "func": __anonymize_password},
+
+ # IS-IS passwords
+ {"path": ["protocols", "isis", "area-password", "plaintext-password"], "func": __anonymize_password},
+ {"base_path": ["protocols", "isis", "interface"], "secret_path": ["password", "plaintext-password"], "func": __anonymize_password},
+
+ # HTTP API servers
+ {"base_path": ["service", "https", "api", "keys", "id"], "secret_path": ["key"], "func": __anonymize_password},
+
+ # Telegraf
+ {"path": ["service", "monitoring", "telegraf", "prometheus-client", "authentication", "password"], "func": __anonymize_password},
+ {"path": ["service", "monitoring", "telegraf", "influxdb", "authentication", "token"], "func": __anonymize_password},
+ {"path": ["service", "monitoring", "telegraf", "azure-data-explorer", "authentication", "client-secret"], "func": __anonymize_password},
+ {"path": ["service", "monitoring", "telegraf", "splunk", "authentication", "token"], "func": __anonymize_password},
+
+ # SNMPv3 passwords
+ {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["privacy", "encrypted-password"], "func": __anonymize_password},
+ {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["privacy", "plaintext-password"], "func": __anonymize_password},
+ {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["auth", "encrypted-password"], "func": __anonymize_password},
+ {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["auth", "encrypted-password"], "func": __anonymize_password},
+]
+
+def __prepare_secret_paths(config_tree, secret_paths):
+ """ Generate a list of secret paths for the current system,
+ adjusted for variable parts such as VRFs and remote access IPsec instances
+ """
+
+ # Fixup for remote-access IPsec local users that are nested under two tag nodes
+ # We generate the list of their paths dynamically
+ ipsec_ra_base = {"base_path": ["vpn", "ipsec", "remote-access", "connection"], "func": __anonymize_password}
+ if config_tree.exists(ipsec_ra_base["base_path"]):
+ for conn in config_tree.list_nodes(ipsec_ra_base["base_path"]):
+ if config_tree.exists(ipsec_ra_base["base_path"] + [conn] + ["authentication", "local-users", "username"]):
+ for u in config_tree.list_nodes(ipsec_ra_base["base_path"] + [conn] + ["authentication", "local-users", "username"]):
+ p = copy.copy(ipsec_ra_base)
+ p["base_path"] = p["base_path"] + [conn] + ["authentication", "local-users", "username"]
+ p["secret_path"] = ["password"]
+ secret_paths.append(p)
+
+ # Fixup for VRFs that may contain routing protocols and other nodes nested under them
+ vrf_paths = []
+ vrf_base_path = ["vrf", "name"]
+ if config_tree.exists(vrf_base_path):
+ for v in config_tree.list_nodes(vrf_base_path):
+ vrf_secret_paths = copy.deepcopy(secret_paths)
+ for sp in vrf_secret_paths:
+ if "base_path" in sp:
+ sp["base_path"] = vrf_base_path + [v] + sp["base_path"]
+ elif "path" in sp:
+ sp["path"] = vrf_base_path + [v] + sp["path"]
+ vrf_paths.append(sp)
+
+ secret_paths = secret_paths + vrf_paths
+
+ # Fixup for user SSH keys, that are nested under a tag node
+ #ssh_key_base_path = {"base_path": ['system', 'login', 'user'], "secret_path": ["authentication", "encrypted-password"], "func": __anonymize_password},
+ user_base_path = ['system', 'login', 'user']
+ ssh_key_paths = []
+ if config_tree.exists(user_base_path):
+ for u in config_tree.list_nodes(user_base_path):
+ kp = {"base_path": user_base_path + [u, "authentication", "public-keys"], "secret_path": ["key"], "func": __anonymize_key}
+ ssh_key_paths.append(kp)
+
+ secret_paths = secret_paths + ssh_key_paths
+
+ # Fixup for OSPF passwords and keys that are nested under OSPF interfaces
+ ospf_base_path = ["protocols", "ospf", "interface"]
+ ospf_paths = []
+ if config_tree.exists(ospf_base_path):
+ for i in config_tree.list_nodes(ospf_base_path):
+ # Plaintext password, there can be only one
+ opp = {"path": ospf_base_path + [i, "authentication", "plaintext-password"], "func": __anonymize_password}
+ md5kp = {"base_path": ospf_base_path + [i, "authentication", "md5", "key-id"], "secret_path": ["md5-key"], "func": __anonymize_password}
+ ospf_paths.append(opp)
+ ospf_paths.append(md5kp)
+
+ secret_paths = secret_paths + ospf_paths
+
+ return secret_paths
+
+def __strip_private(ct, secret_paths):
+ for sp in secret_paths:
+ if "base_path" in sp:
+ if ct.exists(sp["base_path"]):
+ for n in ct.list_nodes(sp["base_path"]):
+ if ct.exists(sp["base_path"] + [n] + sp["secret_path"]):
+ secret = ct.return_value(sp["base_path"] + [n] + sp["secret_path"])
+ ct.set(sp["base_path"] + [n] + sp["secret_path"], value=sp["func"](secret))
+ elif "path" in sp:
+ if ct.exists(sp["path"]):
+ secret = ct.return_value(sp["path"])
+ ct.set(sp["path"], value=sp["func"](secret))
+ else:
+ raise ValueError("Malformed secret path dict, has neither base_path nor path in it ")
+
+ return ct.to_string()
+
+def strip_config_source(config_source):
+ config_tree = vyos.configtree.ConfigTree(config_source)
+ secret_paths = __prepare_secret_paths(config_tree, __secret_paths)
+ stripped_config = __strip_private(config_tree, secret_paths)
+
+ return stripped_config
+
+def strip_config_tree(config_tree):
+ secret_paths = __prepare_secret_paths(config_tree, __secret_paths)
+ return __strip_private(config_tree, secret_paths)