summaryrefslogtreecommitdiff
path: root/src/op_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/op_mode')
-rwxr-xr-xsrc/op_mode/dynamic_dns.py13
-rwxr-xr-xsrc/op_mode/generate_public_key_command.py41
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py230
-rwxr-xr-xsrc/op_mode/monitor_bandwidth_test.sh2
-rwxr-xr-xsrc/op_mode/openconnect-control.py2
-rwxr-xr-xsrc/op_mode/ping.py9
-rwxr-xr-xsrc/op_mode/pki.py845
-rwxr-xr-xsrc/op_mode/show-bond.py92
-rwxr-xr-xsrc/op_mode/show_dhcp.py7
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py6
-rwxr-xr-xsrc/op_mode/show_ipsec_sa.py159
-rwxr-xr-xsrc/op_mode/show_nat66_rules.py19
-rwxr-xr-xsrc/op_mode/show_nat_rules.py60
-rwxr-xr-xsrc/op_mode/show_vrf.py7
-rwxr-xr-xsrc/op_mode/show_wwan.py78
-rwxr-xr-xsrc/op_mode/vpn_ike_sa.py77
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py119
-rwxr-xr-xsrc/op_mode/wireguard.py159
-rwxr-xr-xsrc/op_mode/wireguard_client.py2
19 files changed, 1646 insertions, 281 deletions
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
index 962943896..263a3b6a5 100755
--- a/src/op_mode/dynamic_dns.py
+++ b/src/op_mode/dynamic_dns.py
@@ -36,6 +36,10 @@ update-status: {{ entry.status }}
"""
def show_status():
+ # A ddclient status file must not always exist
+ if not os.path.exists(cache_file):
+ sys.exit(0)
+
data = {
'hosts': []
}
@@ -61,11 +65,10 @@ def show_status():
if ip:
outp['ip'] = ip.split(',')[0]
- if 'atime=' in line:
- atime = line.split('atime=')[1]
- if atime:
- tmp = atime.split(',')[0]
- outp['time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(tmp, base=10)))
+ if 'mtime=' in line:
+ mtime = line.split('mtime=')[1]
+ if mtime:
+ outp['time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(mtime.split(',')[0], base=10)))
if 'status=' in line:
status = line.split('status=')[1]
diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py
new file mode 100755
index 000000000..7a7b6c923
--- /dev/null
+++ b/src/op_mode/generate_public_key_command.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import urllib.parse
+
+import vyos.remote
+
+def get_key(path):
+ url = urllib.parse.urlparse(path)
+ if url.scheme == 'file' or url.scheme == '':
+ with open(os.path.expanduser(path), 'r') as f:
+ key_string = f.read()
+ else:
+ key_string = vyos.remote.get_remote_config(path)
+ return key_string.split()
+
+username = sys.argv[1]
+algorithm, key, identifier = get_key(sys.argv[2])
+
+print('# To add this key as an embedded key, run the following commands:')
+print('configure')
+print(f'set system login user {username} authentication public-keys {identifier} key {key}')
+print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
+print('commit')
+print('save')
+print('exit')
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
new file mode 100755
index 000000000..d45525431
--- /dev/null
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+
+from jinja2 import Template
+from sys import exit
+from socket import getfqdn
+from cryptography.x509.oid import NameOID
+
+from vyos.config import Config
+from vyos.pki import load_certificate
+from vyos.template import render_to_string
+from vyos.util import ask_input
+
+# Apple profiles only support one IKE/ESP encryption cipher and hash, whereas
+# VyOS comes with a multitude of different proposals for a connection.
+#
+# We take all available proposals from the VyOS CLI and ask the user which one
+# he would like to get enabled in his profile - thus there is limited possibility
+# to select a proposal that is not supported on the connection profile.
+#
+# IOS supports IKE-SA encryption algorithms:
+# - DES
+# - 3DES
+# - AES-128
+# - AES-256
+# - AES-128-GCM
+# - AES-256-GCM
+# - ChaCha20Poly1305
+#
+vyos2apple_cipher = {
+ '3des' : '3DES',
+ 'aes128' : 'AES-128',
+ 'aes256' : 'AES-256',
+ 'aes128gcm128' : 'AES-128-GCM',
+ 'aes256gcm128' : 'AES-256-GCM',
+ 'chacha20poly1305' : 'ChaCha20Poly1305',
+}
+
+# Windows supports IKE-SA encryption algorithms:
+# - DES3
+# - AES128
+# - AES192
+# - AES256
+# - GCMAES128
+# - GCMAES192
+# - GCMAES256
+#
+vyos2windows_cipher = {
+ '3des' : 'DES3',
+ 'aes128' : 'AES128',
+ 'aes192' : 'AES192',
+ 'aes256' : 'AES256',
+ 'aes128gcm128' : 'GCMAES128',
+ 'aes192gcm128' : 'GCMAES192',
+ 'aes256gcm128' : 'GCMAES256',
+}
+
+# IOS supports IKE-SA integrity algorithms:
+# - SHA1-96
+# - SHA1-160
+# - SHA2-256
+# - SHA2-384
+# - SHA2-512
+#
+vyos2apple_integrity = {
+ 'sha1' : 'SHA1-96',
+ 'sha1_160' : 'SHA1-160',
+ 'sha256' : 'SHA2-256',
+ 'sha384' : 'SHA2-384',
+ 'sha512' : 'SHA2-512',
+}
+
+# Windows supports IKE-SA integrity algorithms:
+# - SHA1-96
+# - SHA1-160
+# - SHA2-256
+# - SHA2-384
+# - SHA2-512
+#
+vyos2windows_integrity = {
+ 'sha1' : 'SHA196',
+ 'sha256' : 'SHA256',
+ 'aes128gmac' : 'GCMAES128',
+ 'aes192gmac' : 'GCMAES192',
+ 'aes256gmac' : 'GCMAES256',
+}
+
+# IOS 14.2 and later do no support dh-group 1,2 and 5. Supported DH groups would
+# be: 14, 15, 16, 17, 18, 19, 20, 21, 31
+ios_supported_dh_groups = ['14', '15', '16', '17', '18', '19', '20', '21', '31']
+# Windows 10 only allows a limited set of DH groups
+windows_supported_dh_groups = ['1', '2', '14', '24']
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--os', const='all', nargs='?', choices=['ios', 'windows'], help='Operating system used for config generation', required=True)
+parser.add_argument("--connection", action="store", help='IPsec IKEv2 remote-access connection name from CLI', required=True)
+parser.add_argument("--remote", action="store", help='VPN connection remote-address where the client will connect to', required=True)
+parser.add_argument("--profile", action="store", help='IKEv2 profile name used in the profile list on the device')
+parser.add_argument("--name", action="store", help='VPN connection name as seen in the VPN application later')
+args = parser.parse_args()
+
+ipsec_base = ['vpn', 'ipsec']
+config_base = ipsec_base + ['remote-access', 'connection']
+pki_base = ['pki']
+conf = Config()
+if not conf.exists(config_base):
+ exit('IPSec remote-access is not configured!')
+
+profile_name = 'VyOS IKEv2 Profile'
+if args.profile:
+ profile_name = args.profile
+
+vpn_name = 'VyOS IKEv2 VPN'
+if args.name:
+ vpn_name = args.name
+
+conn_base = config_base + [args.connection]
+if not conf.exists(conn_base):
+ exit(f'IPSec remote-access connection "{args.connection}" does not exist!')
+
+data = conf.get_config_dict(conn_base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+data['profile_name'] = profile_name
+data['vpn_name'] = vpn_name
+data['remote'] = args.remote
+# This is a reverse-DNS style unique identifier used to detect duplicate profiles
+tmp = getfqdn().split('.')
+tmp = reversed(tmp)
+data['rfqdn'] = '.'.join(tmp)
+
+pki = conf.get_config_dict(pki_base, get_first_key=True)
+ca_name = data['authentication']['x509']['ca_certificate']
+cert_name = data['authentication']['x509']['certificate']
+
+ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
+cert = load_certificate(pki['certificate'][cert_name]['certificate'])
+
+data['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+data['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+data['ca_cert'] = conf.return_value(pki_base + ['ca', ca_name, 'certificate'])
+
+esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'],
+ key_mangling=('-', '_'), get_first_key=True)
+ike_proposal = conf.get_config_dict(ipsec_base + ['ike-group', data['ike_group'], 'proposal'],
+ key_mangling=('-', '_'), get_first_key=True)
+
+
+# This script works only for Apple iOS/iPadOS and Windows. Both operating systems
+# have different limitations thus we load the limitations based on the operating
+# system used.
+
+vyos2client_cipher = vyos2apple_cipher if args.os == 'ios' else vyos2windows_cipher;
+vyos2client_integrity = vyos2apple_integrity if args.os == 'ios' else vyos2windows_integrity;
+supported_dh_groups = ios_supported_dh_groups if args.os == 'ios' else windows_supported_dh_groups;
+
+# Create a dictionary containing client conform IKE settings
+ike = {}
+count = 1
+for _, proposal in ike_proposal.items():
+ if {'dh_group', 'encryption', 'hash'} <= set(proposal):
+ if (proposal['encryption'] in set(vyos2client_cipher) and
+ proposal['hash'] in set(vyos2client_integrity) and
+ proposal['dh_group'] in set(supported_dh_groups)):
+
+ # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme
+ proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ]
+ proposal['hash'] = vyos2client_integrity[ proposal['hash'] ]
+
+ ike.update( { str(count) : proposal } )
+ count += 1
+
+# Create a dictionary containing Apple conform ESP settings
+esp = {}
+count = 1
+for _, proposal in esp_proposals.items():
+ if {'encryption', 'hash'} <= set(proposal):
+ if proposal['encryption'] in set(vyos2client_cipher) and proposal['hash'] in set(vyos2client_integrity):
+ # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme
+ proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ]
+ proposal['hash'] = vyos2client_integrity[ proposal['hash'] ]
+
+ esp.update( { str(count) : proposal } )
+ count += 1
+try:
+ if len(ike) > 1:
+ # Propare the input questions for the user
+ tmp = '\n'
+ for number, options in ike.items():
+ tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}, DH group {options["dh_group"]}\n'
+ tmp += '\nSelect one of the above IKE groups: '
+ data['ike_encryption'] = ike[ ask_input(tmp, valid_responses=list(ike)) ]
+ else:
+ data['ike_encryption'] = ike['1']
+
+ if len(esp) > 1:
+ tmp = '\n'
+ for number, options in esp.items():
+ tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}\n'
+ tmp += '\nSelect one of the above ESP groups: '
+ data['esp_encryption'] = esp[ ask_input(tmp, valid_responses=list(esp)) ]
+ else:
+ data['esp_encryption'] = esp['1']
+
+except KeyboardInterrupt:
+ exit("Interrupted")
+
+print('\n\n==== <snip> ====')
+if args.os == 'ios':
+ print(render_to_string('ipsec/ios_profile.tmpl', data))
+ print('==== </snip> ====\n')
+ print('Save the XML from above to a new file named "vyos.mobileconfig" and E-Mail it to your phone.')
+elif args.os == 'windows':
+ print(render_to_string('ipsec/windows_profile.tmpl', data))
+ print('==== </snip> ====\n')
diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/monitor_bandwidth_test.sh
index 6da0291c5..900223bca 100755
--- a/src/op_mode/monitor_bandwidth_test.sh
+++ b/src/op_mode/monitor_bandwidth_test.sh
@@ -26,5 +26,5 @@ elif [[ $(dig $1 AAAA +short | grep -v '\.$' | wc -l) -gt 0 ]]; then
OPT="-V"
fi
-/usr/bin/iperf $OPT -c $1
+/usr/bin/iperf $OPT -c $1 $2
diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py
index ef9fe618c..c3cd25186 100755
--- a/src/op_mode/openconnect-control.py
+++ b/src/op_mode/openconnect-control.py
@@ -58,7 +58,7 @@ def main():
is_ocserv_configured()
if args.action == "restart":
- run("systemctl restart ocserv")
+ run("sudo systemctl restart ocserv.service")
sys.exit(0)
elif args.action == "show_sessions":
show_sessions()
diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py
index 29b430d53..2144ab53c 100755
--- a/src/op_mode/ping.py
+++ b/src/op_mode/ping.py
@@ -50,6 +50,11 @@ options = {
'type': '<seconds>',
'help': 'Number of seconds before ping exits'
},
+ 'do-not-fragment': {
+ 'ping': '{command} -M do',
+ 'type': 'noarg',
+ 'help': 'Set DF-bit flag to 1 for no fragmentation'
+ },
'flood': {
'ping': 'sudo {command} -f',
'type': 'noarg',
@@ -215,6 +220,8 @@ if __name__ == '__main__':
try:
ip = socket.gethostbyname(host)
+ except UnicodeError:
+ sys.exit(f'ping: Unknown host: {host}')
except socket.gaierror:
ip = host
@@ -227,4 +234,4 @@ if __name__ == '__main__':
# print(f'{command} {host}')
os.system(f'{command} {host}')
-
+ \ No newline at end of file
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
new file mode 100755
index 000000000..297270cf1
--- /dev/null
+++ b/src/op_mode/pki.py
@@ -0,0 +1,845 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import ipaddress
+import os
+import re
+import sys
+import tabulate
+
+from cryptography import x509
+from cryptography.x509.oid import ExtendedKeyUsageOID
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters
+from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
+from vyos.pki import create_private_key
+from vyos.pki import create_dh_parameters
+from vyos.pki import load_certificate, load_certificate_request, load_private_key, load_crl
+from vyos.pki import verify_certificate
+from vyos.xml import defaults
+from vyos.util import ask_input, ask_yes_no
+from vyos.util import cmd
+
+CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
+
+auth_dir = '/config/auth'
+
+# Helper Functions
+
+def get_default_values():
+ # Fetch default x509 values
+ conf = Config()
+ base = ['pki', 'x509', 'default']
+ x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ default_values = defaults(base)
+ return dict_merge(default_values, x509_defaults)
+
+def get_config_ca_certificate(name=None):
+ # Fetch ca certificates from config
+ conf = Config()
+ base = ['pki', 'ca']
+
+ if not conf.exists(base):
+ return False
+
+ if name:
+ base = base + [name]
+ if not conf.exists(base + ['private', 'key']) or not conf.exists(base + ['certificate']):
+ return False
+
+ return conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+def get_config_certificate(name=None):
+ # Get certificates from config
+ conf = Config()
+ base = ['pki', 'certificate']
+
+ if not conf.exists(base):
+ return False
+
+ if name:
+ base = base + [name]
+ if not conf.exists(base + ['private', 'key']) or not conf.exists(base + ['certificate']):
+ return False
+
+ return conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+def get_certificate_ca(cert, ca_certs):
+ # Find CA certificate for given certificate
+ for ca_name, ca_dict in ca_certs.items():
+ if 'certificate' not in ca_dict:
+ continue
+
+ ca_cert = load_certificate(ca_dict['certificate'])
+
+ if not ca_cert:
+ continue
+
+ if verify_certificate(cert, ca_cert):
+ return ca_name
+ return None
+
+def get_config_revoked_certificates():
+ # Fetch revoked certificates from config
+ conf = Config()
+ ca_base = ['pki', 'ca']
+ cert_base = ['pki', 'certificate']
+
+ certs = []
+
+ if conf.exists(ca_base):
+ ca_certificates = conf.get_config_dict(ca_base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ certs.extend(ca_certificates.values())
+
+ if conf.exists(cert_base):
+ certificates = conf.get_config_dict(cert_base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ certs.extend(certificates.values())
+
+ return [cert_dict for cert_dict in certs if 'revoke' in cert_dict]
+
+def get_revoked_by_serial_numbers(serial_numbers=[]):
+ # Return serial numbers of revoked certificates
+ certs_out = []
+ certs = get_config_certificate()
+ ca_certs = get_config_ca_certificate()
+ if certs:
+ for cert_name, cert_dict in certs.items():
+ if 'certificate' not in cert_dict:
+ continue
+
+ cert = load_certificate(cert_dict['certificate'])
+ if cert.serial_number in serial_numbers:
+ certs_out.append(cert_name)
+ if ca_certs:
+ for cert_name, cert_dict in ca_certs.items():
+ if 'certificate' not in cert_dict:
+ continue
+
+ cert = load_certificate(cert_dict['certificate'])
+ if cert.serial_number in serial_numbers:
+ certs_out.append(cert_name)
+ return certs_out
+
+def install_certificate(name, cert='', private_key=None, key_type=None, key_passphrase=None, is_ca=False):
+ # Show conf commands for installing certificate
+ prefix = 'ca' if is_ca else 'certificate'
+ print("Configure mode commands to install:")
+
+ if cert:
+ cert_pem = "".join(encode_certificate(cert).strip().split("\n")[1:-1])
+ print("set pki %s %s certificate '%s'" % (prefix, name, cert_pem))
+
+ if private_key:
+ key_pem = "".join(encode_private_key(private_key, passphrase=key_passphrase).strip().split("\n")[1:-1])
+ print("set pki %s %s private key '%s'" % (prefix, name, key_pem))
+ if key_passphrase:
+ print("set pki %s %s private password-protected" % (prefix, name))
+
+def install_crl(ca_name, crl):
+ # Show conf commands for installing crl
+ print("Configure mode commands to install CRL:")
+ crl_pem = "".join(encode_certificate(crl).strip().split("\n")[1:-1])
+ print("set pki ca %s crl '%s'" % (ca_name, crl_pem))
+
+def install_dh_parameters(name, params):
+ # Show conf commands for installing dh params
+ print("Configure mode commands to install DH parameters:")
+ dh_pem = "".join(encode_dh_parameters(params).strip().split("\n")[1:-1])
+ print("set pki dh %s parameters '%s'" % (name, dh_pem))
+
+def install_ssh_key(name, public_key, private_key, passphrase=None):
+ # Show conf commands for installing ssh key
+ key_openssh = encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH')
+ username = os.getlogin()
+ type_key_split = key_openssh.split(" ")
+ print("Configure mode commands to install SSH key:")
+ print("set system login user %s authentication public-keys %s key '%s'" % (username, name, type_key_split[1]))
+ print("set system login user %s authentication public-keys %s type '%s'" % (username, name, type_key_split[0]))
+ print("")
+ print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
+
+def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None):
+ # Show conf commands for installing key-pair
+ print("Configure mode commands to install key pair:")
+
+ if public_key:
+ install_public_key = ask_yes_no('Do you want to install the public key?', default=True)
+ public_key_pem = encode_public_key(public_key)
+
+ if install_public_key:
+ install_public_pem = "".join(public_key_pem.strip().split("\n")[1:-1])
+ print("set pki key-pair %s public key '%s'" % (name, install_public_pem))
+ else:
+ print("Public key:")
+ print(public_key_pem)
+
+ if private_key:
+ install_private_key = ask_yes_no('Do you want to install the private key?', default=True)
+ private_key_pem = encode_private_key(private_key, passphrase=passphrase)
+
+ if install_private_key:
+ install_private_pem = "".join(private_key_pem.strip().split("\n")[1:-1])
+ print("set pki key-pair %s private key '%s'" % (name, install_private_pem))
+ if passphrase:
+ print("set pki key-pair %s private password-protected" % (name,))
+ else:
+ print("Private key:")
+ print(private_key_pem)
+
+def install_wireguard_key(name, private_key, public_key):
+ # Show conf commands for installing wireguard key pairs
+ is_interface = re.match(r'^wg[\d]+$', name)
+
+ print("Configure mode commands to install key:")
+ if is_interface:
+ print("set interfaces wireguard %s private-key '%s'" % (name, private_key))
+ print("")
+ print("Public key for use on peer configuration: " + public_key)
+ else:
+ print("set interfaces wireguard [INTERFACE] peer %s public-key '%s'" % (name, public_key))
+ print("")
+ print("Private key for use on peer configuration: " + private_key)
+
+def install_wireguard_psk(name, psk):
+ # Show conf commands for installing wireguard psk
+ print("set interfaces wireguard [INTERFACE] peer %s preshared-key '%s'" % (name, psk))
+
+def ask_passphrase():
+ passphrase = None
+ print("Note: If you plan to use the generated key on this router, do not encrypt the private key.")
+ if ask_yes_no('Do you want to encrypt the private key with a passphrase?'):
+ passphrase = ask_input('Enter passphrase:')
+ return passphrase
+
+def write_file(filename, contents):
+ full_path = os.path.join(auth_dir, filename)
+ directory = os.path.dirname(full_path)
+
+ if not os.path.exists(directory):
+ print('Failed to write file: directory does not exist')
+ return False
+
+ if os.path.exists(full_path) and not ask_yes_no('Do you want to overwrite the existing file?'):
+ return False
+
+ with open(full_path, 'w') as f:
+ f.write(contents)
+
+ print(f'File written to {full_path}')
+
+# Generation functions
+
+def generate_private_key():
+ key_type = ask_input('Enter private key type: [rsa, dsa, ec]', default='rsa', valid_responses=['rsa', 'dsa', 'ec'])
+
+ size_valid = []
+ size_default = 0
+
+ if key_type in ['rsa', 'dsa']:
+ size_default = 2048
+ size_valid = [512, 1024, 2048, 4096]
+ elif key_type == 'ec':
+ size_default = 256
+ size_valid = [224, 256, 384, 521]
+
+ size = ask_input('Enter private key bits:', default=size_default, numeric_only=True, valid_responses=size_valid)
+
+ return create_private_key(key_type, size), key_type
+
+def parse_san_string(san_string):
+ if not san_string:
+ return None
+
+ output = []
+ san_split = san_string.strip().split(",")
+
+ for pair_str in san_split:
+ tag, value = pair_str.strip().split(":", 1)
+ if tag == 'ipv4':
+ output.append(ipaddress.IPv4Address(value))
+ elif tag == 'ipv6':
+ output.append(ipaddress.IPv6Address(value))
+ elif tag == 'dns':
+ output.append(value)
+ return output
+
+def generate_certificate_request(private_key=None, key_type=None, return_request=False, name=None, install=False, file=False, ask_san=True):
+ if not private_key:
+ private_key, key_type = generate_private_key()
+
+ default_values = get_default_values()
+ subject = {}
+ subject['country'] = ask_input('Enter country code:', default=default_values['country'])
+ subject['state'] = ask_input('Enter state:', default=default_values['state'])
+ subject['locality'] = ask_input('Enter locality:', default=default_values['locality'])
+ subject['organization'] = ask_input('Enter organization name:', default=default_values['organization'])
+ subject['common_name'] = ask_input('Enter common name:', default='vyos.io')
+ subject_alt_names = None
+
+ if ask_san and ask_yes_no('Do you want to configure Subject Alternative Names?'):
+ print("Enter alternative names in a comma separate list, example: ipv4:1.1.1.1,ipv6:fe80::1,dns:vyos.net")
+ san_string = ask_input('Enter Subject Alternative Names:')
+ subject_alt_names = parse_san_string(san_string)
+
+ cert_req = create_certificate_request(subject, private_key, subject_alt_names)
+
+ if return_request:
+ return cert_req
+
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_certificate(cert_req))
+ print(encode_private_key(private_key, passphrase=passphrase))
+ return None
+
+ if install:
+ print("Certificate request:")
+ print(encode_certificate(cert_req) + "\n")
+ install_certificate(name, private_key=private_key, key_type=key_type, key_passphrase=passphrase, is_ca=False)
+
+ if file:
+ write_file(f'{name}.csr', encode_certificate(cert_req))
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+
+def generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False, is_sub_ca=False):
+ valid_days = ask_input('Enter how many days certificate will be valid:', default='365' if not is_ca else '1825', numeric_only=True)
+ cert_type = None
+ if not is_ca:
+ cert_type = ask_input('Enter certificate type: (client, server)', default='server', valid_responses=['client', 'server'])
+ return create_certificate(cert_req, ca_cert, ca_private_key, valid_days, cert_type, is_ca, is_sub_ca)
+
+def generate_ca_certificate(name, install=False, file=False):
+ private_key, key_type = generate_private_key()
+ cert_req = generate_certificate_request(private_key, key_type, return_request=True, ask_san=False)
+ cert = generate_certificate(cert_req, cert_req, private_key, is_ca=True)
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_certificate(cert))
+ print(encode_private_key(private_key, passphrase=passphrase))
+ return None
+
+ if install:
+ install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True)
+
+ if file:
+ write_file(f'{name}.pem', encode_certificate(cert))
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+
+def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
+ ca_dict = get_config_ca_certificate(ca_name)
+
+ if not ca_dict:
+ print(f"CA certificate or private key for '{ca_name}' not found")
+ return None
+
+ ca_cert = load_certificate(ca_dict['certificate'])
+
+ if not ca_cert:
+ print("Failed to load signing CA certificate, aborting")
+ return None
+
+ ca_private = ca_dict['private']
+ ca_private_passphrase = None
+ if 'password_protected' in ca_private:
+ ca_private_passphrase = ask_input('Enter signing CA private key passphrase:')
+ ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase)
+
+ if not ca_private_key:
+ print("Failed to load signing CA private key, aborting")
+ return None
+
+ private_key = None
+ key_type = None
+
+ cert_req = None
+ if not ask_yes_no('Do you already have a certificate request?'):
+ private_key, key_type = generate_private_key()
+ cert_req = generate_certificate_request(private_key, key_type, return_request=True, ask_san=False)
+ else:
+ print("Paste certificate request and press enter:")
+ lines = []
+ curr_line = ''
+ while True:
+ curr_line = input().strip()
+ if not curr_line or curr_line == CERT_REQ_END:
+ break
+ lines.append(curr_line)
+
+ if not lines:
+ print("Aborted")
+ return None
+
+ wrap = lines[0].find('-----') < 0 # Only base64 pasted, add the CSR tags for parsing
+ cert_req = load_certificate_request("\n".join(lines), wrap)
+
+ if not cert_req:
+ print("Invalid certificate request")
+ return None
+
+ cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=True, is_sub_ca=True)
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_certificate(cert))
+ print(encode_private_key(private_key, passphrase=passphrase))
+ return None
+
+ if install:
+ install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True)
+
+ if file:
+ write_file(f'{name}.pem', encode_certificate(cert))
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+
+def generate_certificate_sign(name, ca_name, install=False, file=False):
+ ca_dict = get_config_ca_certificate(ca_name)
+
+ if not ca_dict:
+ print(f"CA certificate or private key for '{ca_name}' not found")
+ return None
+
+ ca_cert = load_certificate(ca_dict['certificate'])
+
+ if not ca_cert:
+ print("Failed to load CA certificate, aborting")
+ return None
+
+ ca_private = ca_dict['private']
+ ca_private_passphrase = None
+ if 'password_protected' in ca_private:
+ ca_private_passphrase = ask_input('Enter CA private key passphrase:')
+ ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase)
+
+ if not ca_private_key:
+ print("Failed to load CA private key, aborting")
+ return None
+
+ private_key = None
+ key_type = None
+
+ cert_req = None
+ if not ask_yes_no('Do you already have a certificate request?'):
+ private_key, key_type = generate_private_key()
+ cert_req = generate_certificate_request(private_key, key_type, return_request=True)
+ else:
+ print("Paste certificate request and press enter:")
+ lines = []
+ curr_line = ''
+ while True:
+ curr_line = input().strip()
+ if not curr_line or curr_line == CERT_REQ_END:
+ break
+ lines.append(curr_line)
+
+ if not lines:
+ print("Aborted")
+ return None
+
+ wrap = lines[0].find('-----') < 0 # Only base64 pasted, add the CSR tags for parsing
+ cert_req = load_certificate_request("\n".join(lines), wrap)
+
+ if not cert_req:
+ print("Invalid certificate request")
+ return None
+
+ cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False)
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_certificate(cert))
+ print(encode_private_key(private_key, passphrase=passphrase))
+ return None
+
+ if install:
+ install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=False)
+
+ if file:
+ write_file(f'{name}.pem', encode_certificate(cert))
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+
+def generate_certificate_selfsign(name, install=False, file=False):
+ private_key, key_type = generate_private_key()
+ cert_req = generate_certificate_request(private_key, key_type, return_request=True)
+ cert = generate_certificate(cert_req, cert_req, private_key, is_ca=False)
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_certificate(cert))
+ print(encode_private_key(private_key, passphrase=passphrase))
+ return None
+
+ if install:
+ install_certificate(name, cert, private_key=private_key, key_type=key_type, key_passphrase=passphrase, is_ca=False)
+
+ if file:
+ write_file(f'{name}.pem', encode_certificate(cert))
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+
+def generate_certificate_revocation_list(ca_name, install=False, file=False):
+ ca_dict = get_config_ca_certificate(ca_name)
+
+ if not ca_dict:
+ print(f"CA certificate or private key for '{ca_name}' not found")
+ return None
+
+ ca_cert = load_certificate(ca_dict['certificate'])
+
+ if not ca_cert:
+ print("Failed to load CA certificate, aborting")
+ return None
+
+ ca_private = ca_dict['private']
+ ca_private_passphrase = None
+ if 'password_protected' in ca_private:
+ ca_private_passphrase = ask_input('Enter CA private key passphrase:')
+ ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase)
+
+ if not ca_private_key:
+ print("Failed to load CA private key, aborting")
+ return None
+
+ revoked_certs = get_config_revoked_certificates()
+ to_revoke = []
+
+ for cert_dict in revoked_certs:
+ if 'certificate' not in cert_dict:
+ continue
+
+ cert_data = cert_dict['certificate']
+
+ try:
+ cert = load_certificate(cert_data)
+
+ if cert.issuer == ca_cert.subject:
+ to_revoke.append(cert.serial_number)
+ except ValueError:
+ continue
+
+ if not to_revoke:
+ print("No revoked certificates to add to the CRL")
+ return None
+
+ crl = create_certificate_revocation_list(ca_cert, ca_private_key, to_revoke)
+
+ if not crl:
+ print("Failed to create CRL")
+ return None
+
+ if not install and not file:
+ print(encode_certificate(crl))
+ return None
+
+ if install:
+ install_crl(ca_name, crl)
+
+ if file:
+ write_file(f'{name}.crl', encode_certificate(crl))
+
+def generate_ssh_keypair(name, install=False, file=False):
+ private_key, key_type = generate_private_key()
+ public_key = private_key.public_key()
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH'))
+ print("")
+ print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
+ return None
+
+ if install:
+ install_ssh_key(name, public_key, private_key, passphrase)
+
+ if file:
+ write_file(f'{name}.pem', encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH'))
+ write_file(f'{name}.key', encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
+
+def generate_dh_parameters(name, install=False, file=False):
+ bits = ask_input('Enter DH parameters key size:', default=2048, numeric_only=True)
+
+ print("Generating parameters...")
+
+ dh_params = create_dh_parameters(bits)
+ if not dh_params:
+ print("Failed to create DH parameters")
+ return None
+
+ if not install and not file:
+ print("DH Parameters:")
+ print(encode_dh_parameters(dh_params))
+
+ if install:
+ install_dh_parameters(name, dh_params)
+
+ if file:
+ write_file(f'{name}.pem', encode_dh_parameters(dh_params))
+
+def generate_keypair(name, install=False, file=False):
+ private_key, key_type = generate_private_key()
+ public_key = private_key.public_key()
+ passphrase = ask_passphrase()
+
+ if not install and not file:
+ print(encode_public_key(public_key))
+ print("")
+ print(encode_private_key(private_key, passphrase=passphrase))
+ return None
+
+ if install:
+ install_keypair(name, key_type, private_key, public_key, passphrase)
+
+ if file:
+ write_file(f'{name}.pem', encode_public_key(public_key))
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+
+def generate_openvpn_key(name, install=False, file=False):
+ result = cmd('openvpn --genkey secret /dev/stdout | grep -o "^[^#]*"')
+
+ if not result:
+ print("Failed to generate OpenVPN key")
+ return None
+
+ if not install and not file:
+ print(result)
+ return None
+
+ if install:
+ key_lines = result.split("\n")
+ key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
+ key_version = '1'
+
+ version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', result) # Future-proofing (hopefully)
+ if version_search:
+ key_version = version_search[1]
+
+ print("Configure mode commands to install OpenVPN key:")
+ print("set pki openvpn shared-secret %s key '%s'" % (name, key_data))
+ print("set pki openvpn shared-secret %s version '%s'" % (name, key_version))
+
+ if file:
+ write_file(f'{name}.key', result)
+
+def generate_wireguard_key(name, install=False, file=False):
+ private_key = cmd('wg genkey')
+ public_key = cmd('wg pubkey', input=private_key)
+
+ if not install:
+ print("Private key: " + private_key)
+ print("Public key: " + public_key)
+ return None
+
+ if install:
+ install_wireguard_key(name, private_key, public_key)
+
+ if file:
+ write_file(f'{name}_public.key', public_key)
+ write_file(f'{name}_private.key', private_key)
+
+def generate_wireguard_psk(name, install=False, file=False):
+ psk = cmd('wg genpsk')
+
+ if not install and not file:
+ print("Pre-shared key:")
+ print(psk)
+ return None
+
+ if install:
+ install_wireguard_psk(name, psk)
+
+ if file:
+ write_file(f'{name}.key', psk)
+
+# Show functions
+
+def show_certificate_authority(name=None):
+ headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent']
+ data = []
+ certs = get_config_ca_certificate()
+ if certs:
+ for cert_name, cert_dict in certs.items():
+ if name and name != cert_name:
+ continue
+ if 'certificate' not in cert_dict:
+ continue
+
+ cert = load_certificate(cert_dict['certificate'])
+ parent_ca_name = get_certificate_ca(cert, certs)
+ cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0]
+
+ if not parent_ca_name or parent_ca_name == cert_name:
+ parent_ca_name = 'N/A'
+
+ if not cert:
+ continue
+
+ have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No'
+ data.append([cert_name, cert.subject.rfc4514_string(), cert_issuer_cn, cert.not_valid_before, cert.not_valid_after, have_private, parent_ca_name])
+
+ print("Certificate Authorities:")
+ print(tabulate.tabulate(data, headers))
+
+def show_certificate(name=None):
+ headers = ['Name', 'Type', 'Subject CN', 'Issuer CN', 'Issued', 'Expiry', 'Revoked', 'Private Key', 'CA Present']
+ data = []
+ certs = get_config_certificate()
+ if certs:
+ ca_certs = get_config_ca_certificate()
+
+ for cert_name, cert_dict in certs.items():
+ if name and name != cert_name:
+ continue
+ if 'certificate' not in cert_dict:
+ continue
+
+ cert = load_certificate(cert_dict['certificate'])
+
+ if not cert:
+ continue
+
+ ca_name = get_certificate_ca(cert, ca_certs)
+ cert_subject_cn = cert.subject.rfc4514_string().split(",")[0]
+ cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0]
+ cert_type = 'Unknown'
+ ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
+ if ext and ExtendedKeyUsageOID.SERVER_AUTH in ext.value:
+ cert_type = 'Server'
+ elif ext and ExtendedKeyUsageOID.CLIENT_AUTH in ext.value:
+ cert_type = 'Client'
+
+ revoked = 'Yes' if 'revoke' in cert_dict else 'No'
+ have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No'
+ have_ca = f'Yes ({ca_name})' if ca_name else 'No'
+ data.append([
+ cert_name, cert_type, cert_subject_cn, cert_issuer_cn,
+ cert.not_valid_before, cert.not_valid_after,
+ revoked, have_private, have_ca])
+
+ print("Certificates:")
+ print(tabulate.tabulate(data, headers))
+
+def show_crl(name=None):
+ headers = ['CA Name', 'Updated', 'Revokes']
+ data = []
+ certs = get_config_ca_certificate()
+ if certs:
+ for cert_name, cert_dict in certs.items():
+ if name and name != cert_name:
+ continue
+ if 'crl' not in cert_dict:
+ continue
+
+ crls = cert_dict['crl']
+ if isinstance(crls, str):
+ crls = [crls]
+
+ for crl_data in cert_dict['crl']:
+ crl = load_crl(crl_data)
+
+ if not crl:
+ continue
+
+ certs = get_revoked_by_serial_numbers([revoked.serial_number for revoked in crl])
+ data.append([cert_name, crl.last_update, ", ".join(certs)])
+
+ print("Certificate Revocation Lists:")
+ print(tabulate.tabulate(data, headers))
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='PKI action', required=True)
+
+ # X509
+ parser.add_argument('--ca', help='Certificate Authority', required=False)
+ parser.add_argument('--certificate', help='Certificate', required=False)
+ parser.add_argument('--crl', help='Certificate Revocation List', required=False)
+ parser.add_argument('--sign', help='Sign certificate with specified CA', required=False)
+ parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true')
+
+ # SSH
+ parser.add_argument('--ssh', help='SSH Key', required=False)
+
+ # DH
+ parser.add_argument('--dh', help='DH Parameters', required=False)
+
+ # Key pair
+ parser.add_argument('--keypair', help='Key pair', required=False)
+
+ # OpenVPN
+ parser.add_argument('--openvpn', help='OpenVPN TLS key', required=False)
+
+ # Wireguard
+ parser.add_argument('--wireguard', help='Wireguard', action='store_true')
+ parser.add_argument('--key', help='Wireguard key pair', required=False)
+ parser.add_argument('--psk', help='Wireguard pre shared key', required=False)
+
+ # Global
+ parser.add_argument('--file', help='Write generated keys into specified filename', action='store_true')
+ parser.add_argument('--install', help='Install generated keys into running-config', action='store_true')
+
+ args = parser.parse_args()
+
+ try:
+ if args.action == 'generate':
+ if args.ca:
+ if args.sign:
+ generate_ca_certificate_sign(args.ca, args.sign, install=args.install, file=args.file)
+ else:
+ generate_ca_certificate(args.ca, install=args.install, file=args.file)
+ elif args.certificate:
+ if args.sign:
+ generate_certificate_sign(args.certificate, args.sign, install=args.install, file=args.file)
+ elif args.self_sign:
+ generate_certificate_selfsign(args.certificate, install=args.install, file=args.file)
+ else:
+ generate_certificate_request(name=args.certificate, install=args.install)
+ elif args.crl:
+ generate_certificate_revocation_list(args.crl, install=args.install, file=args.file)
+ elif args.ssh:
+ generate_ssh_keypair(args.ssh, install=args.install, file=args.file)
+ elif args.dh:
+ generate_dh_parameters(args.dh, install=args.install, file=args.file)
+ elif args.keypair:
+ generate_keypair(args.keypair, install=args.install, file=args.file)
+ elif args.openvpn:
+ generate_openvpn_key(args.openvpn, install=args.install, file=args.file)
+ elif args.wireguard:
+ if args.key:
+ generate_wireguard_key(args.key, install=args.install, file=args.file)
+ elif args.psk:
+ generate_wireguard_psk(args.psk, install=args.install, file=args.file)
+ elif args.action == 'show':
+ if args.ca:
+ show_certificate_authority(None if args.ca == 'all' else args.ca)
+ elif args.certificate:
+ show_certificate(None if args.certificate == 'all' else args.certificate)
+ elif args.crl:
+ show_crl(None if args.crl == 'all' else args.crl)
+ else:
+ show_certificate_authority()
+ show_certificate()
+ show_crl()
+ except KeyboardInterrupt:
+ print("Aborted")
+ sys.exit(0)
diff --git a/src/op_mode/show-bond.py b/src/op_mode/show-bond.py
new file mode 100755
index 000000000..edf7847fc
--- /dev/null
+++ b/src/op_mode/show-bond.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jinja2
+
+from argparse import ArgumentParser
+from vyos.ifconfig import Section
+from vyos.ifconfig import BondIf
+from vyos.util import read_file
+
+from sys import exit
+
+parser = ArgumentParser()
+parser.add_argument("--slaves", action="store_true", help="Show LLDP neighbors on all interfaces")
+parser.add_argument("--interface", action="store", help="Show LLDP neighbors on specific interface")
+
+args = parser.parse_args()
+
+all_bonds = Section.interfaces('bonding')
+# we are not interested in any bond vlan interface
+all_bonds = [x for x in all_bonds if '.' not in x]
+
+TMPL_BRIEF = """Interface Mode State Link Slaves
+{% for interface in data %}
+{{ "%-12s" | format(interface.ifname) }} {{ "%-22s" | format(interface.mode) }} {{ "%-8s" | format(interface.admin_state) }} {{ "%-6s" | format(interface.oper_state) }} {{ interface.members | join(' ') }}
+{% endfor %}
+"""
+
+TMPL_INDIVIDUAL_BOND = """Interface RX: bytes packets TX: bytes packets
+{{ "%-16s" | format(data.ifname) }} {{ "%-10s" | format(data.rx_bytes) }} {{ "%-11s" | format(data.rx_packets) }} {{ "%-10s" | format(data.tx_bytes) }} {{ data.tx_packets }}
+{% for member in data.members if data.members is defined %}
+ {{ "%-12s" | format(member.ifname) }} {{ "%-10s" | format(member.rx_bytes) }} {{ "%-11s" | format(member.rx_packets) }} {{ "%-10s" | format(member.tx_bytes) }} {{ member.tx_packets }}
+{% endfor %}
+"""
+
+if args.slaves and args.interface:
+ exit('Can not use both --slaves and --interfaces option at the same time')
+ parser.print_help()
+
+elif args.slaves:
+ data = []
+ template = TMPL_BRIEF
+ for bond in all_bonds:
+ tmp = BondIf(bond)
+ cfg_dict = {}
+ cfg_dict['ifname'] = bond
+ cfg_dict['mode'] = tmp.get_mode()
+ cfg_dict['admin_state'] = tmp.get_admin_state()
+ cfg_dict['oper_state'] = tmp.operational.get_state()
+ cfg_dict['members'] = tmp.get_slaves()
+ data.append(cfg_dict)
+
+elif args.interface:
+ template = TMPL_INDIVIDUAL_BOND
+ data = {}
+ data['ifname'] = args.interface
+ data['rx_bytes'] = read_file(f'/sys/class/net/{args.interface}/statistics/rx_bytes')
+ data['rx_packets'] = read_file(f'/sys/class/net/{args.interface}/statistics/rx_packets')
+ data['tx_bytes'] = read_file(f'/sys/class/net/{args.interface}/statistics/tx_bytes')
+ data['tx_packets'] = read_file(f'/sys/class/net/{args.interface}/statistics/tx_packets')
+
+ # each bond member interface has its own statistics
+ data['members'] = []
+ for member in BondIf(args.interface).get_slaves():
+ tmp = {}
+ tmp['ifname'] = member
+ tmp['rx_bytes'] = read_file(f'/sys/class/net/{member}/statistics/rx_bytes')
+ tmp['rx_packets'] = read_file(f'/sys/class/net/{member}/statistics/rx_packets')
+ tmp['tx_bytes'] = read_file(f'/sys/class/net/{member}/statistics/tx_bytes')
+ tmp['tx_packets'] = read_file(f'/sys/class/net/{member}/statistics/tx_packets')
+ data['members'].append(tmp)
+
+else:
+ parser.print_help()
+ exit(1)
+
+tmpl = jinja2.Template(template, trim_blocks=True)
+config_text = tmpl.render(data=data)
+print(config_text)
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
index ff1e3cc56..4df275e04 100755
--- a/src/op_mode/show_dhcp.py
+++ b/src/op_mode/show_dhcp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -27,8 +27,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
from vyos.config import Config
-from vyos.util import call
-
+from vyos.util import is_systemd_service_running
lease_file = "/config/dhcpd.leases"
pool_key = "shared-networkname"
@@ -217,7 +216,7 @@ if __name__ == '__main__':
exit(0)
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if call('systemctl -q is-active isc-dhcp-server.service') != 0:
+ if not is_systemd_service_running('isc-dhcp-server.service'):
print("WARNING: DHCP server is configured but not started. Data may be stale.")
if args.leases:
diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py
index f70f04298..1f987ff7b 100755
--- a/src/op_mode/show_dhcpv6.py
+++ b/src/op_mode/show_dhcpv6.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -27,7 +27,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
from vyos.config import Config
-from vyos.util import call
+from vyos.util import is_systemd_service_running
lease_file = "/config/dhcpdv6.leases"
pool_key = "shared-networkname"
@@ -202,7 +202,7 @@ if __name__ == '__main__':
exit(0)
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if call('systemctl -q is-active isc-dhcp-server6.service') != 0:
+ if not is_systemd_service_running('isc-dhcp-server6.service'):
print("WARNING: DHCPv6 server is configured but not started. Data may be stale.")
if args.leases:
diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py
index 645a0571d..e491267fd 100755
--- a/src/op_mode/show_ipsec_sa.py
+++ b/src/op_mode/show_ipsec_sa.py
@@ -23,39 +23,24 @@ import hurry.filesize
import vyos.util
+def format_output(conns, sas):
+ sa_data = []
-try:
- session = vici.Session()
- sas = session.list_sas()
-except PermissionError:
- print("You do not have a permission to connect to the IPsec daemon")
- sys.exit(1)
-except ConnectionRefusedError:
- print("IPsec is not runing")
- sys.exit(1)
-except Exception as e:
- print("An error occured: {0}".format(e))
- sys.exit(1)
-
-sa_data = []
-
-for sa in sas:
- # list_sas() returns a list of single-item dicts
- for peer in sa:
- parent_sa = sa[peer]
- child_sas = parent_sa["child-sas"]
- installed_sas = {k: v for k, v in child_sas.items() if v["state"] == b"INSTALLED"}
+ for peer, parent_conn in conns.items():
+ if peer not in sas:
+ continue
+
+ parent_sa = sas[peer]
+ child_sas = parent_sa['child-sas']
+ installed_sas = {v['name'].decode(): v for k, v in child_sas.items() if v["state"] == b"INSTALLED"}
# parent_sa["state"] = IKE state, child_sas["state"] = ESP state
+ state = 'down'
+ uptime = 'N/A'
+
if parent_sa["state"] == b"ESTABLISHED" and installed_sas:
state = "up"
- else:
- state = "down"
-
- if state == "up":
uptime = vyos.util.seconds_to_human(parent_sa["established"].decode())
- else:
- uptime = "N/A"
remote_host = parent_sa["remote-host"].decode()
remote_id = parent_sa["remote-id"].decode()
@@ -64,51 +49,77 @@ for sa in sas:
remote_id = "N/A"
# The counters can only be obtained from the child SAs
- if not installed_sas:
- data = [peer, state, "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"]
- sa_data.append(data)
- else:
- for csa in installed_sas:
- isa = installed_sas[csa]
- csa_name = isa['name']
- csa_name = csa_name.decode()
-
- bytes_in = hurry.filesize.size(int(isa["bytes-in"].decode()))
- bytes_out = hurry.filesize.size(int(isa["bytes-out"].decode()))
- bytes_str = "{0}/{1}".format(bytes_in, bytes_out)
-
- pkts_in = hurry.filesize.size(int(isa["packets-in"].decode()), system=hurry.filesize.si)
- pkts_out = hurry.filesize.size(int(isa["packets-out"].decode()), system=hurry.filesize.si)
- pkts_str = "{0}/{1}".format(pkts_in, pkts_out)
- # Remove B from <1K values
- pkts_str = re.sub(r'B', r'', pkts_str)
-
- enc = isa["encr-alg"].decode()
- if "encr-keysize" in isa:
- key_size = isa["encr-keysize"].decode()
- else:
- key_size = ""
- if "integ-alg" in isa:
- hash = isa["integ-alg"].decode()
- else:
- hash = ""
- if "dh-group" in isa:
- dh_group = isa["dh-group"].decode()
- else:
- dh_group = ""
-
- proposal = enc
- if key_size:
- proposal = "{0}_{1}".format(proposal, key_size)
- if hash:
- proposal = "{0}/{1}".format(proposal, hash)
- if dh_group:
- proposal = "{0}/{1}".format(proposal, dh_group)
-
- data = [csa_name, state, uptime, bytes_str, pkts_str, remote_host, remote_id, proposal]
+ for child_conn in parent_conn['children']:
+ if child_conn not in installed_sas:
+ data = [child_conn, "down", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"]
sa_data.append(data)
-
-headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"]
-sa_data = sorted(sa_data, key=lambda peer: peer[0])
-output = tabulate.tabulate(sa_data, headers)
-print(output)
+ continue
+
+ isa = installed_sas[child_conn]
+ csa_name = isa['name']
+ csa_name = csa_name.decode()
+
+ bytes_in = hurry.filesize.size(int(isa["bytes-in"].decode()))
+ bytes_out = hurry.filesize.size(int(isa["bytes-out"].decode()))
+ bytes_str = "{0}/{1}".format(bytes_in, bytes_out)
+
+ pkts_in = hurry.filesize.size(int(isa["packets-in"].decode()), system=hurry.filesize.si)
+ pkts_out = hurry.filesize.size(int(isa["packets-out"].decode()), system=hurry.filesize.si)
+ pkts_str = "{0}/{1}".format(pkts_in, pkts_out)
+ # Remove B from <1K values
+ pkts_str = re.sub(r'B', r'', pkts_str)
+
+ enc = isa["encr-alg"].decode()
+ if "encr-keysize" in isa:
+ key_size = isa["encr-keysize"].decode()
+ else:
+ key_size = ""
+ if "integ-alg" in isa:
+ hash = isa["integ-alg"].decode()
+ else:
+ hash = ""
+ if "dh-group" in isa:
+ dh_group = isa["dh-group"].decode()
+ else:
+ dh_group = ""
+
+ proposal = enc
+ if key_size:
+ proposal = "{0}_{1}".format(proposal, key_size)
+ if hash:
+ proposal = "{0}/{1}".format(proposal, hash)
+ if dh_group:
+ proposal = "{0}/{1}".format(proposal, dh_group)
+
+ data = [csa_name, state, uptime, bytes_str, pkts_str, remote_host, remote_id, proposal]
+ sa_data.append(data)
+ return sa_data
+
+if __name__ == '__main__':
+ try:
+ session = vici.Session()
+ conns = {}
+ sas = {}
+
+ for conn in session.list_conns():
+ for key in conn:
+ conns[key] = conn[key]
+
+ for sa in session.list_sas():
+ for key in sa:
+ sas[key] = sa[key]
+
+ headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"]
+ sa_data = format_output(conns, sas)
+ sa_data = sorted(sa_data, key=lambda peer: peer[0])
+ output = tabulate.tabulate(sa_data, headers)
+ print(output)
+ except PermissionError:
+ print("You do not have a permission to connect to the IPsec daemon")
+ sys.exit(1)
+ except ConnectionRefusedError:
+ print("IPsec is not runing")
+ sys.exit(1)
+ except Exception as e:
+ print("An error occured: {0}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/show_nat66_rules.py b/src/op_mode/show_nat66_rules.py
index a25e146a7..967ec9d37 100755
--- a/src/op_mode/show_nat66_rules.py
+++ b/src/op_mode/show_nat66_rules.py
@@ -68,7 +68,7 @@ if args.source or args.destination:
rule = comment.replace('SRC-NAT66-','')
rule = rule.replace('DST-NAT66-','')
chain = data['chain']
- if not (args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING'):
+ if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')):
continue
interface = dict_search('match.right', data['expr'][0])
srcdest = dict_search('match.right.prefix.addr', data['expr'][2])
@@ -79,16 +79,19 @@ if args.source or args.destination:
else:
srcdest = dict_search('match.right', data['expr'][2])
- tran_addr = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
- if tran_addr:
- addr_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
- if addr_tmp:
- srcdest = srcdest + '/' + str(addr_tmp)
+ tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
+ if tran_addr_json:
+ if isinstance(srcdest_json,str):
+ tran_addr = tran_addr_json
+
+ if 'prefix' in tran_addr_json:
+ addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
+ len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
+ if addr_tmp:
+ tran_addr = addr_tmp + '/' + str(len_tmp)
else:
if 'masquerade' in data['expr'][3]:
tran_addr = 'masquerade'
- else:
- tran_addr = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
print(format_nat66_rule.format(rule, srcdest, tran_addr, interface))
diff --git a/src/op_mode/show_nat_rules.py b/src/op_mode/show_nat_rules.py
index 68cff61c8..0f40ecabe 100755
--- a/src/op_mode/show_nat_rules.py
+++ b/src/op_mode/show_nat_rules.py
@@ -33,9 +33,9 @@ if args.source or args.destination:
tmp = cmd('sudo nft -j list table ip nat')
tmp = json.loads(tmp)
- format_nat66_rule = '{0: <10} {1: <50} {2: <50} {3: <10}'
- print(format_nat66_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
- print(format_nat66_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
+ format_nat_rule = '{0: <10} {1: <50} {2: <50} {3: <10}'
+ print(format_nat_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
+ print(format_nat_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp)
for idx in range(0, len(data_json)):
@@ -63,30 +63,50 @@ if args.source or args.destination:
rule = int(''.join(list(filter(str.isdigit, comment))))
chain = data['chain']
- if not (args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING'):
+ if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')):
continue
interface = dict_search('match.right', data['expr'][0])
- srcdest = dict_search('match.right.prefix.addr', data['expr'][1])
- if srcdest:
- addr_tmp = dict_search('match.right.prefix.len', data['expr'][1])
- if addr_tmp:
- srcdest = srcdest + '/' + str(addr_tmp)
- else:
- srcdest = dict_search('match.right', data['expr'][1])
- tran_addr = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
- if tran_addr:
- addr_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
- if addr_tmp:
- srcdest = srcdest + '/' + str(addr_tmp)
+ srcdest = ''
+ for i in [1, 2]:
+ srcdest_json = dict_search('match.right', data['expr'][i])
+ if not srcdest_json:
+ continue
+
+ if isinstance(srcdest_json,str):
+ srcdest += srcdest_json + ' '
+ elif 'prefix' in srcdest_json:
+ addr_tmp = dict_search('match.right.prefix.addr', data['expr'][i])
+ len_tmp = dict_search('match.right.prefix.len', data['expr'][i])
+ if addr_tmp and len_tmp:
+ srcdest = addr_tmp + '/' + str(len_tmp) + ' '
+ elif 'set' in srcdest_json:
+ if isinstance(srcdest_json['set'][0],str):
+ srcdest += 'port ' + str(srcdest_json['set'][0]) + ' '
+ else:
+ port_range = srcdest_json['set'][0]['range']
+ srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' '
+
+ tran_addr = ''
+ tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
+ if tran_addr_json:
+ if isinstance(tran_addr_json,str):
+ tran_addr = tran_addr_json
+ elif 'prefix' in tran_addr_json:
+ addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
+ len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
+ if addr_tmp and len_tmp:
+ tran_addr = addr_tmp + '/' + str(len_tmp)
else:
if 'masquerade' in data['expr'][3]:
tran_addr = 'masquerade'
elif 'log' in data['expr'][3]:
continue
- else:
- tran_addr = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
-
- print(format_nat66_rule.format(rule, srcdest, tran_addr, interface))
+
+ tran_port = dict_search('snat.port' if args.source else 'dnat.port', data['expr'][3])
+ if tran_port:
+ tran_addr += ' port ' + str(tran_port)
+
+ print(format_nat_rule.format(rule, srcdest, tran_addr, interface))
exit(0)
else:
diff --git a/src/op_mode/show_vrf.py b/src/op_mode/show_vrf.py
index 94358c6e4..3c7a90205 100755
--- a/src/op_mode/show_vrf.py
+++ b/src/op_mode/show_vrf.py
@@ -20,12 +20,11 @@ from json import loads
from vyos.util import cmd
-vrf_out_tmpl = """
-VRF name state mac address flags interfaces
+vrf_out_tmpl = """VRF name state mac address flags interfaces
-------- ----- ----------- ----- ----------
-{% for v in vrf %}
+{%- for v in vrf %}
{{"%-16s"|format(v.ifname)}} {{ "%-8s"|format(v.operstate | lower())}} {{"%-17s"|format(v.address | lower())}} {{ v.flags|join(',')|lower()}} {{v.members|join(',')|lower()}}
-{% endfor %}
+{%- endfor %}
"""
diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py
new file mode 100755
index 000000000..249dda2a5
--- /dev/null
+++ b/src/op_mode/show_wwan.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+
+from sys import exit
+from vyos.util import cmd
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--model", help="Get module model", action="store_true")
+parser.add_argument("--revision", help="Get module revision", action="store_true")
+parser.add_argument("--capabilities", help="Get module capabilities", action="store_true")
+parser.add_argument("--imei", help="Get module IMEI/ESN/MEID", action="store_true")
+parser.add_argument("--imsi", help="Get module IMSI", action="store_true")
+parser.add_argument("--msisdn", help="Get module MSISDN", action="store_true")
+parser.add_argument("--sim", help="Get SIM card status", action="store_true")
+parser.add_argument("--signal", help="Get current RF signal info", action="store_true")
+parser.add_argument("--firmware", help="Get current RF signal info", action="store_true")
+
+required = parser.add_argument_group('Required arguments')
+required.add_argument("--interface", help="WWAN interface name, e.g. wwan0", required=True)
+
+def qmi_cmd(device, command, silent=False):
+ tmp = cmd(f'qmicli --device={device} --device-open-proxy {command}')
+ tmp = tmp.replace(f'[{cdc}] ', '')
+ if not silent:
+ # skip first line as this only holds the info headline
+ for line in tmp.splitlines()[1:]:
+ print(line.lstrip())
+ return tmp
+
+if __name__ == '__main__':
+ args = parser.parse_args()
+
+ # remove the WWAN prefix from the interface, required for the CDC interface
+ if_num = args.interface.replace('wwan','')
+ cdc = f'/dev/cdc-wdm{if_num}'
+
+ if args.model:
+ qmi_cmd(cdc, '--dms-get-model')
+ elif args.capabilities:
+ qmi_cmd(cdc, '--dms-get-capabilities')
+ qmi_cmd(cdc, '--dms-get-band-capabilities')
+ elif args.revision:
+ qmi_cmd(cdc, '--dms-get-revision')
+ elif args.imei:
+ qmi_cmd(cdc, '--dms-get-ids')
+ elif args.imsi:
+ qmi_cmd(cdc, '--dms-uim-get-imsi')
+ elif args.msisdn:
+ qmi_cmd(cdc, '--dms-get-msisdn')
+ elif args.sim:
+ qmi_cmd(cdc, '--uim-get-card-status')
+ elif args.signal:
+ qmi_cmd(cdc, '--nas-get-signal-info')
+ qmi_cmd(cdc, '--nas-get-rf-band-info')
+ elif args.firmware:
+ tmp = qmi_cmd(cdc, '--dms-get-manufacturer', silent=True)
+ if 'Sierra Wireless' in tmp:
+ qmi_cmd(cdc, '--dms-swi-get-current-firmware')
+ else:
+ qmi_cmd(cdc, '--dms-get-software-version')
+ else:
+ parser.print_help()
+ exit(1)
diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py
new file mode 100755
index 000000000..00f34564a
--- /dev/null
+++ b/src/op_mode/vpn_ike_sa.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import re
+import sys
+import vici
+
+from vyos.util import process_named_running
+
+ike_sa_peer_prefix = """\
+Peer ID / IP Local ID / IP
+------------ -------------"""
+
+ike_sa_tunnel_prefix = """
+
+ State IKEVer Encrypt Hash D-H Group NAT-T A-Time L-Time
+ ----- ------ ------- ---- --------- ----- ------ ------"""
+
+def s(byte_string):
+ return str(byte_string, 'utf-8')
+
+def ike_sa(peer, nat):
+ session = vici.Session()
+ sas = session.list_sas()
+ peers = []
+ for conn in sas:
+ for name, sa in conn.items():
+ if peer and not name.startswith('peer_' + peer):
+ continue
+ if name.startswith('peer_') and name in peers:
+ continue
+ if nat and 'nat-local' not in sa:
+ continue
+ peers.append(name)
+ remote_str = f'{s(sa["remote-host"])} {s(sa["remote-id"])}' if s(sa['remote-id']) != '%any' else s(sa["remote-host"])
+ local_str = f'{s(sa["local-host"])} {s(sa["local-id"])}' if s(sa['local-id']) != '%any' else s(sa["local-host"])
+ print(ike_sa_peer_prefix)
+ print('%-39s %-39s' % (remote_str, local_str))
+ state = 'up' if 'state' in sa and s(sa['state']) == 'ESTABLISHED' else 'down'
+ version = 'IKEv' + s(sa['version'])
+ encryption = f'{s(sa["encr-alg"])}' if 'encr-alg' in sa else 'n/a'
+ if 'encr-keysize' in sa:
+ encryption += '_' + s(sa["encr-keysize"])
+ integrity = s(sa['integ-alg']) if 'integ-alg' in sa else 'n/a'
+ dh_group = s(sa['dh-group']) if 'dh-group' in sa else 'n/a'
+ natt = 'yes' if 'nat-local' in sa and s(sa['nat-local']) == 'yes' else 'no'
+ atime = s(sa['established']) if 'established' in sa else '0'
+ ltime = s(sa['rekey-time']) if 'rekey_time' in sa else '0'
+ print(ike_sa_tunnel_prefix)
+ print(' %-6s %-6s %-12s %-13s %-14s %-6s %-7s %-7s\n' % (state, version, encryption, integrity, dh_group, natt, atime, ltime))
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--peer', help='Peer name', required=False)
+ parser.add_argument('--nat', help='NAT Traversal', required=False)
+
+ args = parser.parse_args()
+
+ if not process_named_running('charon'):
+ print("IPSec Process NOT Running")
+ sys.exit(0)
+
+ ike_sa(args.peer, args.nat)
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
new file mode 100755
index 000000000..06e227ccf
--- /dev/null
+++ b/src/op_mode/vpn_ipsec.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import argparse
+from subprocess import TimeoutExpired
+
+from vyos.util import call
+
+SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+
+def get_peer_connections(peer, tunnel, return_all = False):
+ search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
+ matches = []
+ with open(SWANCTL_CONF, 'r') as f:
+ for line in f.readlines():
+ result = re.match(search, line)
+ if result:
+ suffix = f'tunnel_{tunnel}' if tunnel.isnumeric() else tunnel
+ if return_all or (result[2] == suffix):
+ matches.append(result[1])
+ return matches
+
+def reset_peer(peer, tunnel):
+ if not peer:
+ print('Invalid peer, aborting')
+ return
+
+ conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all'))
+
+ if not conns:
+ print('Tunnel(s) not found, aborting')
+ return
+
+ result = True
+ for conn in conns:
+ try:
+ call(f'sudo /usr/sbin/ipsec down {conn}', timeout = 10)
+ call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
+ except TimeoutExpired as e:
+ print(f'Timed out while resetting {conn}')
+ result = False
+
+
+ print('Peer reset result: ' + ('success' if result else 'failed'))
+
+def get_profile_connection(profile, tunnel = None):
+ search = rf'(dmvpn-{profile}-[\w]+)' if tunnel == 'all' else rf'(dmvpn-{profile}-{tunnel})'
+ with open(SWANCTL_CONF, 'r') as f:
+ for line in f.readlines():
+ result = re.search(search, line)
+ if result:
+ return result[1]
+ return None
+
+def reset_profile(profile, tunnel):
+ if not profile:
+ print('Invalid profile, aborting')
+ return
+
+ if not tunnel:
+ print('Invalid tunnel, aborting')
+ return
+
+ conn = get_profile_connection(profile)
+
+ if not conn:
+ print('Profile not found, aborting')
+ return
+
+ call(f'sudo /usr/sbin/ipsec down {conn}')
+ result = call(f'sudo /usr/sbin/ipsec up {conn}')
+
+ print('Profile reset result: ' + ('success' if result == 0 else 'failed'))
+
+def debug_peer(peer, tunnel):
+ if not peer or peer == "all":
+ call('sudo /usr/sbin/ipsec statusall')
+ return
+
+ if not tunnel or tunnel == 'all':
+ tunnel = ''
+
+ conn = get_peer_connections(peer, tunnel)
+
+ if not conns:
+ print('Peer not found, aborting')
+ return
+
+ for conn in conns:
+ call(f'sudo /usr/sbin/ipsec statusall | grep {conn}')
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Control action', required=True)
+ parser.add_argument('--name', help='Name for peer reset', required=False)
+ parser.add_argument('--tunnel', help='Specific tunnel of peer', required=False)
+
+ args = parser.parse_args()
+
+ if args.action == 'reset-peer':
+ reset_peer(args.name, args.tunnel)
+ elif args.action == "reset-profile":
+ reset_profile(args.name, args.tunnel)
+ elif args.action == "vpn-debug":
+ debug_peer(args.name, args.tunnel)
diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py
deleted file mode 100755
index e08bc983a..000000000
--- a/src/op_mode/wireguard.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import argparse
-import os
-import sys
-import shutil
-import syslog as sl
-import re
-
-from vyos.config import Config
-from vyos.ifconfig import WireGuardIf
-from vyos.util import cmd
-from vyos.util import run
-from vyos.util import check_kmod
-from vyos import ConfigError
-
-dir = r'/config/auth/wireguard'
-psk = dir + '/preshared.key'
-
-k_mod = 'wireguard'
-
-def generate_keypair(pk, pub):
- """ generates a keypair which is stored in /config/auth/wireguard """
- old_umask = os.umask(0o027)
- if run(f'wg genkey | tee {pk} | wg pubkey > {pub}') != 0:
- raise ConfigError("wireguard key-pair generation failed")
- else:
- sl.syslog(
- sl.LOG_NOTICE, "new keypair wireguard key generated in " + dir)
- os.umask(old_umask)
-
-
-def genkey(location):
- """ helper function to check, regenerate the keypair """
- pk = "{}/private.key".format(location)
- pub = "{}/public.key".format(location)
- old_umask = os.umask(0o027)
- if os.path.exists(pk) and os.path.exists(pub):
- try:
- choice = input(
- "You already have a wireguard key-pair, do you want to re-generate? [y/n] ")
- if choice == 'y' or choice == 'Y':
- generate_keypair(pk, pub)
- except KeyboardInterrupt:
- sys.exit(0)
- else:
- """ if keypair is bing executed from a running iso """
- if not os.path.exists(location):
- run(f'sudo mkdir -p {location}')
- run(f'sudo chgrp vyattacfg {location}')
- run(f'sudo chmod 750 {location}')
- generate_keypair(pk, pub)
- os.umask(old_umask)
-
-
-def showkey(key):
- """ helper function to show privkey or pubkey """
- if os.path.exists(key):
- print (open(key).read().strip())
- else:
- print ("{} not found".format(key))
-
-
-def genpsk():
- """
- generates a preshared key and shows it on stdout,
- it's stored only in the cli config
- """
-
- psk = cmd('wg genpsk')
- print(psk)
-
-def list_key_dirs():
- """ lists all dirs under /config/auth/wireguard """
- if os.path.exists(dir):
- nks = next(os.walk(dir))[1]
- for nk in nks:
- print (nk)
-
-def del_key_dir(kname):
- """ deletes /config/auth/wireguard/<kname> """
- kdir = "{0}/{1}".format(dir,kname)
- if not os.path.isdir(kdir):
- print ("named keypair {} not found".format(kname))
- return 1
- shutil.rmtree(kdir)
-
-
-if __name__ == '__main__':
- check_kmod(k_mod)
- parser = argparse.ArgumentParser(description='wireguard key management')
- parser.add_argument(
- '--genkey', action="store_true", help='generate key-pair')
- parser.add_argument(
- '--showpub', action="store_true", help='shows public key')
- parser.add_argument(
- '--showpriv', action="store_true", help='shows private key')
- parser.add_argument(
- '--genpsk', action="store_true", help='generates preshared-key')
- parser.add_argument(
- '--location', action="store", help='key location within {}'.format(dir))
- parser.add_argument(
- '--listkdir', action="store_true", help='lists named keydirectories')
- parser.add_argument(
- '--delkdir', action="store_true", help='removes named keydirectories')
- parser.add_argument(
- '--showinterface', action="store", help='shows interface details')
- args = parser.parse_args()
-
- try:
- if args.genkey:
- if args.location:
- genkey("{0}/{1}".format(dir, args.location))
- else:
- genkey("{}/default".format(dir))
- if args.showpub:
- if args.location:
- showkey("{0}/{1}/public.key".format(dir, args.location))
- else:
- showkey("{}/default/public.key".format(dir))
- if args.showpriv:
- if args.location:
- showkey("{0}/{1}/private.key".format(dir, args.location))
- else:
- showkey("{}/default/private.key".format(dir))
- if args.genpsk:
- genpsk()
- if args.listkdir:
- list_key_dirs()
- if args.showinterface:
- try:
- intf = WireGuardIf(args.showinterface, create=False, debug=False)
- print(intf.operational.show_interface())
- # the interface does not exists
- except Exception:
- pass
- if args.delkdir:
- if args.location:
- del_key_dir(args.location)
- else:
- del_key_dir("default")
-
- except ConfigError as e:
- print(e)
- sys.exit(1)
diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py
index 7a620a01e..7661254da 100755
--- a/src/op_mode/wireguard_client.py
+++ b/src/op_mode/wireguard_client.py
@@ -38,7 +38,7 @@ To enable this configuration on a VyOS router you can use the following commands
{% for addr in address if address is defined %}
set interfaces wireguard {{ interface }} peer {{ name }} allowed-ips '{{ addr }}'
{% endfor %}
-set interfaces wireguard {{ interface }} peer {{ name }} pubkey '{{ pubkey }}'
+set interfaces wireguard {{ interface }} peer {{ name }} public-key '{{ pubkey }}'
"""
client_config = """