diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/service_ipoe-server.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/service_pppoe-server.py | 5 | ||||
-rw-r--r-- | src/migration-scripts/ipoe-server/3-to-4 | 30 | ||||
-rw-r--r-- | src/migration-scripts/pppoe-server/10-to-11 | 30 | ||||
-rwxr-xr-x | src/op_mode/pki.py | 41 | ||||
-rw-r--r-- | src/opt/vyatta/etc/shell/level/users/allowed-op | 1 | ||||
-rw-r--r-- | src/opt/vyatta/etc/shell/level/users/allowed-op.in | 1 | ||||
-rwxr-xr-x | src/services/vyos-http-api-server | 62 |
8 files changed, 158 insertions, 14 deletions
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 16c82e591..c7e3ef033 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -70,6 +70,8 @@ def verify(ipoe): if 'client_subnet' in iface_config and 'vlan' in iface_config: raise ConfigError('Option "client-subnet" and "vlan" are mutually exclusive, ' 'use "client-ip-pool" instead!') + if 'vlan_mon' in iface_config and not 'vlan' in iface_config: + raise ConfigError('Option "vlan-mon" requires "vlan" to be set!') verify_accel_ppp_authentication(ipoe, local_users=False) verify_accel_ppp_ip_pool(ipoe) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 566a7b149..ac697c509 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -121,9 +121,12 @@ def verify(pppoe): raise ConfigError('At least one listen interface must be defined!') # Check is interface exists in the system - for interface in pppoe['interface']: + for interface, interface_config in pppoe['interface'].items(): verify_interface_exists(pppoe, interface, warning_only=True) + if 'vlan_mon' in interface_config and not 'vlan' in interface_config: + raise ConfigError('Option "vlan-mon" requires "vlan" to be set!') + return None diff --git a/src/migration-scripts/ipoe-server/3-to-4 b/src/migration-scripts/ipoe-server/3-to-4 new file mode 100644 index 000000000..3bad9756d --- /dev/null +++ b/src/migration-scripts/ipoe-server/3-to-4 @@ -0,0 +1,30 @@ +# 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/>. + +# Add the "vlan-mon" option to the configuration to prevent it +# from disappearing from the configuration file + +from vyos.configtree import ConfigTree + +base = ['service', 'ipoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for interface in config.list_nodes(base + ['interface']): + base_path = base + ['interface', interface] + if config.exists(base_path + ['vlan']): + config.set(base_path + ['vlan-mon']) diff --git a/src/migration-scripts/pppoe-server/10-to-11 b/src/migration-scripts/pppoe-server/10-to-11 new file mode 100644 index 000000000..6bc138b5c --- /dev/null +++ b/src/migration-scripts/pppoe-server/10-to-11 @@ -0,0 +1,30 @@ +# 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/>. + +# Add the "vlan-mon" option to the configuration to prevent it +# from disappearing from the configuration file + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for interface in config.list_nodes(base + ['interface']): + base_path = base + ['interface', interface] + if config.exists(base_path + ['vlan']): + config.set(base_path + ['vlan-mon']) diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index 84b080023..ab613e5c4 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -316,7 +316,13 @@ def generate_certificate_request(private_key=None, key_type=None, return_request default_values = get_default_values() subject = {} - subject['country'] = ask_input('Enter country code:', default=default_values['country']) + while True: + country = ask_input('Enter country code:', default=default_values['country']) + if len(country) != 2: + print("Country name must be a 2 character country code") + continue + subject['country'] = country + break 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']) @@ -693,7 +699,7 @@ def generate_wireguard_psk(interface=None, peer=None, install=False): print(f'Pre-shared key: {psk}') # Import functions -def import_ca_certificate(name, path=None, key_path=None): +def import_ca_certificate(name, path=None, key_path=None, no_prompt=False, passphrase=None): if path: if not os.path.exists(path): print(f'File not found: {path}') @@ -717,19 +723,20 @@ def import_ca_certificate(name, path=None, key_path=None): return key = None - passphrase = ask_input('Enter private key passphrase: ') or None + if not no_prompt: + passphrase = ask_input('Enter private key passphrase: ') or None with open(key_path) as f: key_data = f.read() key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False) if not key: - print(f'Invalid private key or passphrase: {path}') + print(f'Invalid private key or passphrase: {key_path}') return install_certificate(name, private_key=key, is_ca=True) -def import_certificate(name, path=None, key_path=None): +def import_certificate(name, path=None, key_path=None, no_prompt=False, passphrase=None): if path: if not os.path.exists(path): print(f'File not found: {path}') @@ -753,14 +760,15 @@ def import_certificate(name, path=None, key_path=None): return key = None - passphrase = ask_input('Enter private key passphrase: ') or None + if not no_prompt: + passphrase = ask_input('Enter private key passphrase: ') or None with open(key_path) as f: key_data = f.read() key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False) if not key: - print(f'Invalid private key or passphrase: {path}') + print(f'Invalid private key or passphrase: {key_path}') return install_certificate(name, private_key=key, is_ca=False) @@ -799,7 +807,7 @@ def import_dh_parameters(name, path): install_dh_parameters(name, dh) -def import_keypair(name, path=None, key_path=None): +def import_keypair(name, path=None, key_path=None, no_prompt=False, passphrase=None): if path: if not os.path.exists(path): print(f'File not found: {path}') @@ -823,14 +831,15 @@ def import_keypair(name, path=None, key_path=None): return key = None - passphrase = ask_input('Enter private key passphrase: ') or None + if not no_prompt: + passphrase = ask_input('Enter private key passphrase: ') or None with open(key_path) as f: key_data = f.read() key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False) if not key: - print(f'Invalid private key or passphrase: {path}') + print(f'Invalid private key or passphrase: {key_path}') return install_keypair(name, None, private_key=key, prompt=False) @@ -1011,6 +1020,9 @@ if __name__ == '__main__': parser.add_argument('--filename', help='Write certificate into specified filename', action='store') parser.add_argument('--key-filename', help='Write key into specified filename', action='store') + parser.add_argument('--no-prompt', action='store_true', help='Perform action non-interactively') + parser.add_argument('--passphrase', help='A passphrase to decrypt the private key') + args = parser.parse_args() try: @@ -1054,15 +1066,18 @@ if __name__ == '__main__': generate_wireguard_psk(args.interface, peer=args.peer, install=args.install) elif args.action == 'import': if args.ca: - import_ca_certificate(args.ca, path=args.filename, key_path=args.key_filename) + import_ca_certificate(args.ca, path=args.filename, key_path=args.key_filename, + no_prompt=args.no_prompt, passphrase=args.passphrase) elif args.certificate: - import_certificate(args.certificate, path=args.filename, key_path=args.key_filename) + import_certificate(args.certificate, path=args.filename, key_path=args.key_filename, + no_prompt=args.no_prompt, passphrase=args.passphrase) elif args.crl: import_crl(args.crl, args.filename) elif args.dh: import_dh_parameters(args.dh, args.filename) elif args.keypair: - import_keypair(args.keypair, path=args.filename, key_path=args.key_filename) + import_keypair(args.keypair, path=args.filename, key_path=args.key_filename, + no_prompt=args.no_prompt, passphrase=args.passphrase) elif args.openvpn: import_openvpn_secret(args.openvpn, args.filename) elif args.action == 'show': diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op b/src/opt/vyatta/etc/shell/level/users/allowed-op index 74c45af37..381fd26e5 100644 --- a/src/opt/vyatta/etc/shell/level/users/allowed-op +++ b/src/opt/vyatta/etc/shell/level/users/allowed-op @@ -6,6 +6,7 @@ clear connect delete disconnect +execute exit force monitor diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op.in b/src/opt/vyatta/etc/shell/level/users/allowed-op.in index 1976904e4..9752f99a2 100644 --- a/src/opt/vyatta/etc/shell/level/users/allowed-op.in +++ b/src/opt/vyatta/etc/shell/level/users/allowed-op.in @@ -2,6 +2,7 @@ clear connect delete disconnect +execute exit force monitor diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 7f5233c6b..97633577d 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -212,6 +212,22 @@ class ImageModel(ApiModel): } } +class ImportPkiModel(ApiModel): + op: StrictStr + path: List[StrictStr] + passphrase: StrictStr = None + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "import_pki", + "path": ["op", "mode", "path"], + "passphrase": "passphrase", + } + } + + class ContainerImageModel(ApiModel): op: StrictStr name: StrictStr = None @@ -585,6 +601,14 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel, return success(msg) +def create_path_import_pki_no_prompt(path): + correct_paths = ['ca', 'certificate', 'key-pair'] + if path[1] not in correct_paths: + return False + path[1] = '--' + path[1].replace('-', '') + path[3] = '--key-filename' + return path[1:] + @app.post('/configure') def configure_op(data: Union[ConfigureModel, ConfigureListModel], @@ -814,6 +838,44 @@ def reset_op(data: ResetModel): return success(res) +@app.post('/import-pki') +def import_pki(data: ImportPkiModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + lock.acquire() + + try: + if op == 'import-pki': + # need to get rid or interactive mode for private key + if len(path) == 5 and path[3] in ['key-file', 'private-key']: + path_no_prompt = create_path_import_pki_no_prompt(path) + if not path_no_prompt: + return error(400, f"Invalid command: {' '.join(path)}") + if data.passphrase: + path_no_prompt += ['--passphrase', data.passphrase] + res = session.import_pki_no_prompt(path_no_prompt) + else: + res = session.import_pki(path) + if not res[0].isdigit(): + return error(400, res) + # commit changes + session.commit() + res = res.split('. ')[0] + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + finally: + lock.release() + + return success(res) + @app.post('/poweroff') def poweroff_op(data: PoweroffModel): session = app.state.vyos_session |