summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-01-08 18:18:42 +0100
committerChristian Poessinger <christian@poessinger.com>2021-01-08 18:18:42 +0100
commit43a9441cb80a14fff791bbd89e88a3c2ac99e3ab (patch)
tree2bf9c09fab5c95efa73fc2f7601890466fa9b99f /src
parent13a58d38b3dc8065a8ba71904e143e3d69aab638 (diff)
parent23f55c4bcbe5475ed98d57cf54b645ef0c2cc1a8 (diff)
downloadvyos-1x-43a9441cb80a14fff791bbd89e88a3c2ac99e3ab.tar.gz
vyos-1x-43a9441cb80a14fff791bbd89e88a3c2ac99e3ab.zip
Merge branch 'current' of github.com:vyos/vyos-1x into equuleus
* 'current' of github.com:vyos/vyos-1x: (30 commits) smoketest: dummy: fix indent smoketest: bridge: bond: enable ip subsystem tests smoketest: interfaces: dhcpv6pd final fix smoketest: ethernet: fix link-speed loop test Debian: add build-dependency on python3-jinja2 smoketest: ethernet: verify() speed/duplex must both be auto or discrete smoketest: interfaces: report skipped tests smoketest: ethernet: bugfixes for dhcpc6 and unknown interfaces Debian: add python3-psutil build dependency smoketest: ethernet: check for error on non existing interface vyos.configverify: provide generic helper to check for interface existence smoketest: interfaces: fix dhcpv6 pd testcase when using multiple interfaces login: radius: T3192: migrate to get_config_dict() ssh: T2635: harden Jinja2 template and daemon startup ssh: T2635: change sshd_config path to /run/sshd login: radius: T3192: support IPv6 server(s) and source-address xml: include: provide generic include for disable node xml: radius: T3192: split individual nodes to discrete includes bgp: T2174: verify() existence of route-map and prefix-list smoketest: interfaces: test dhcpv6 pd sla-id auto increment ...
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py19
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py8
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py83
-rwxr-xr-xsrc/conf_mode/protocols_isis.py33
-rwxr-xr-xsrc/conf_mode/ssh.py7
-rwxr-xr-xsrc/conf_mode/system-login.py482
6 files changed, 294 insertions, 338 deletions
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index bc102826f..e7f0cd6a5 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -23,13 +23,13 @@ from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
from vyos.configverify import verify_dhcpv6
+from vyos.configverify import verify_eapol
from vyos.configverify import verify_interface_exists
+from vyos.configverify import verify_mirror
from vyos.configverify import verify_mtu
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
-from vyos.configverify import verify_eapol
-from vyos.configverify import verify_mirror
from vyos.ifconfig import EthernetIf
from vyos.template import render
from vyos.util import call
@@ -59,15 +59,13 @@ def verify(ethernet):
if 'deleted' in ethernet:
return None
- verify_interface_exists(ethernet)
-
- if ethernet.get('speed', None) == 'auto':
- if ethernet.get('duplex', None) != 'auto':
- raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too')
+ ifname = ethernet['ifname']
+ verify_interface_exists(ifname)
- if ethernet.get('duplex', None) == 'auto':
- if ethernet.get('speed', None) != 'auto':
- raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too')
+ # No need to check speed and duplex keys as both have default values.
+ if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or
+ (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')):
+ raise ConfigError('Speed/Duplex missmatch. Must be both auto or manually configured')
verify_mtu(ethernet)
verify_mtu_ipv6(ethernet)
@@ -77,7 +75,6 @@ def verify(ethernet):
verify_eapol(ethernet)
verify_mirror(ethernet)
- ifname = ethernet['ifname']
# verify offloading capabilities
if 'offload' in ethernet and 'rps' in ethernet['offload']:
if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'):
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 1a7e9a96d..ffeb57784 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.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
@@ -24,10 +24,11 @@ from vyos.configdict import dict_merge
from vyos.configdict import get_interface_dict
from vyos.configdict import node_changed
from vyos.configdict import leaf_node_changed
-from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_mtu_ipv6
+from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
from vyos.ifconfig import GREIf
from vyos.ifconfig import GRETapIf
@@ -122,6 +123,9 @@ def verify(tunnel):
if 'local_ip' in tunnel and is_ipv6(tunnel['local_ip']):
raise ConfigError('Can not use local IPv6 address is for mGRE tunnels')
+ if 'source_interface' in tunnel:
+ verify_interface_exists(tunnel['source_interface'])
+
def generate(tunnel):
return None
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index a3f32fd2d..678be5066 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -17,10 +17,11 @@
from sys import exit
from vyos.config import Config
-from vyos.util import call
-from vyos.util import dict_search
+from vyos.configdict import dict_merge
from vyos.template import render
from vyos.template import render_to_string
+from vyos.util import call
+from vyos.util import dict_search
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -42,6 +43,16 @@ def get_config():
if not conf.exists(base + ['route-map']):
call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ')
+ # We also need some additional information from the config,
+ # prefix-lists and route-maps for instance.
+ base = ['policy']
+ tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # As we only support one ASN (later checked in begin of verify()) we add the
+ # new information only to the first AS number
+ asn = next(iter(bgp))
+ # Merge policy dict into bgp dict
+ bgp[asn] = dict_merge(tmp, bgp[asn])
+
return bgp
def verify(bgp):
@@ -50,32 +61,64 @@ def verify(bgp):
# Check if declared more than one ASN
if len(bgp) > 1:
- raise ConfigError('Only one BGP AS can be defined!')
+ raise ConfigError('Only one BGP AS number can be defined!')
for asn, asn_config in bgp.items():
+ import pprint
+ pprint.pprint(asn_config)
+
# Common verification for both peer-group and neighbor statements
- for neigh in ['neighbor', 'peer_group']:
+ for neighbor in ['neighbor', 'peer_group']:
# bail out early if there is no neighbor or peer-group statement
# this also saves one indention level
- if neigh not in asn_config:
+ if neighbor not in asn_config:
+ print(f'no {neighbor} found in config')
continue
- #for neighbor, config in asn_config[neigh].items():
- '''
- # These checks need to be modified. Because peer-group can be declared without 'remote-as'.
- # When 'remote-as' configured for specific neighbor in peer-group. For example
- #
-
- set protocols nbgp 65001 neighbor 100.64.0.2 peer-group 'FOO'
- set protocols nbgp 65001 neighbor 100.64.0.2 remote-as '65002'
- set protocols nbgp 65001 peer-group FOO
-
- '''
- #if 'remote_as' not in config and 'peer_group' not in config:
- # raise ConfigError(f'BGP remote-as must be specified for "{neighbor}"!')
+ for peer, peer_config in asn_config[neighbor].items():
+ # Only regular "neighbor" statement can have a peer-group set
+ # Check if the configure peer-group exists
+ if 'peer_group' in peer_config:
+ peer_group = peer_config['peer_group']
+ if peer_group not in asn_config['peer_group']:
+ raise ConfigError(f'Specified peer-group "{peer_group}" for '\
+ f'neighbor "{neighbor}" does not exist!')
+
+ # Some checks can/must only be done on a neighbor and nor a peer-group
+ if neighbor == 'neighbor':
+ # remote-as must be either set explicitly for the neighbor
+ # or for the entire peer-group
+ if 'remote_as' not in peer_config:
+ if 'peer_group' not in peer_config or 'remote_as' not in asn_config['peer_group'][peer_config['peer_group']]:
+ raise ConfigError('Remote AS must be set for neighbor or peer-group!')
+
+ for afi in ['ipv4_unicast', 'ipv6_unicast']:
+ # Bail out early if address family is not configured
+ if 'address_family' not in peer_config or afi not in peer_config['address_family']:
+ continue
+
+ afi_config = peer_config['address_family'][afi]
+ # Validate if configured Prefix list exists
+ if 'prefix_list' in afi_config:
+ for tmp in ['import', 'export']:
+ if tmp in afi_config['prefix_list']:
+ if afi == 'ipv4_unicast':
+ prefix_list = afi_config['prefix_list'][tmp]
+ if 'prefix_list' not in asn_config or prefix_list not in asn_config['prefix_list']:
+ raise ConfigError(f'prefix-list "{prefix_list}" used for "{tmp}" does not exist!')
+ if afi == 'ipv6_unicast':
+ prefix_list = afi_config['prefix_list6'][tmp]
+ if 'prefix_list6' not in asn_config or prefix_list not in asn_config['prefix_list6']:
+ raise ConfigError(f'prefix-list "{prefix_list}" used for "{tmp}" does not exist!')
+
+
+ if 'route_map' in afi_config:
+ for tmp in ['import', 'export']:
+ if tmp in afi_config['route_map']:
+ route_map = afi_config['route_map'][tmp]
+ if 'route_map' not in asn_config or route_map not in asn_config['route_map']:
+ raise ConfigError(f'route-map "{route_map}" used for "{tmp}" does not exist!')
- #if 'remote_as' in config and 'peer_group' in config:
- # raise ConfigError(f'BGP peer-group member "{neighbor}" cannot override remote-as of peer-group!')
return None
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 97ab79583..b7afad473 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -22,6 +22,7 @@ from vyos.config import Config
from vyos.configdict import node_changed
from vyos import ConfigError
from vyos.util import call
+from vyos.util import dict_search
from vyos.template import render
from vyos.template import render_to_string
from vyos import frr
@@ -48,7 +49,7 @@ def verify(isis):
# If more then one isis process is defined (Frr only supports one)
# http://docs.frrouting.org/en/latest/isisd.html#isis-router
if len(isis) > 1:
- raise ConfigError('Only one isis process can be definded')
+ raise ConfigError('Only one isis process can be defined')
# If network entity title (net) not defined
if 'net' not in isis_config:
@@ -63,7 +64,7 @@ def verify(isis):
if {'md5', 'plaintext_password'} <= set(isis_config['encryption']):
raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!')
- # If one param from deley set, but not set others
+ # If one param from delay set, but not set others
if 'spf_delay_ietf' in isis_config:
required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn']
exist_timers = []
@@ -85,6 +86,34 @@ def verify(isis):
if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level:
raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level))
+ # Segment routing checks
+ if dict_search('segment_routing', isis_config):
+ if dict_search('segment_routing.global_block', isis_config):
+ high_label_value = dict_search('segment_routing.global_block.high_label_value', isis_config)
+ low_label_value = dict_search('segment_routing.global_block.low_label_value', isis_config)
+ # If segment routing global block high value is blank, throw error
+ if low_label_value and not high_label_value:
+ raise ConfigError('Segment routing global block high value must not be left blank')
+ # If segment routing global block low value is blank, throw error
+ if high_label_value and not low_label_value:
+ raise ConfigError('Segment routing global block low value must not be left blank')
+ # If segment routing global block low value is higher than the high value, throw error
+ if int(low_label_value) > int(high_label_value):
+ raise ConfigError('Segment routing global block low value must be lower than high value')
+
+ if dict_search('segment_routing.local_block', isis_config):
+ high_label_value = dict_search('segment_routing.local_block.high_label_value', isis_config)
+ low_label_value = dict_search('segment_routing.local_block.low_label_value', isis_config)
+ # If segment routing local block high value is blank, throw error
+ if low_label_value and not high_label_value:
+ raise ConfigError('Segment routing local block high value must not be left blank')
+ # If segment routing local block low value is blank, throw error
+ if high_label_value and not low_label_value:
+ raise ConfigError('Segment routing local block low value must not be left blank')
+ # If segment routing local block low value is higher than the high value, throw error
+ if int(low_label_value) > int(high_label_value):
+ raise ConfigError('Segment routing local block low value must be lower than high value')
+
return None
def generate(isis):
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 8f99053d2..28e606663 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -28,7 +28,7 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_file = r'/run/ssh/sshd_config'
+config_file = r'/run/sshd/sshd_config'
systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf'
def get_config(config=None):
@@ -68,6 +68,8 @@ def generate(ssh):
render(config_file, 'ssh/sshd_config.tmpl', ssh)
render(systemd_override, 'ssh/override.conf.tmpl', ssh)
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
return None
@@ -76,9 +78,6 @@ def apply(ssh):
# SSH access is removed in the commit
call('systemctl stop ssh.service')
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
-
if ssh:
call('systemctl restart ssh.service')
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 39bad717d..82accd404 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -16,33 +16,30 @@
import os
-from crypt import crypt, METHOD_SHA512
-from netifaces import interfaces
+from crypt import crypt
+from crypt import METHOD_SHA512
from psutil import users
-from pwd import getpwall, getpwnam
+from pwd import getpwall
+from pwd import getpwnam
from spwd import getspnam
from sys import exit
from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configverify import verify_vrf
from vyos.template import render
-from vyos.util import cmd, call, DEVNULL, chmod_600, chmod_755
+from vyos.template import is_ipv4
+from vyos.util import cmd
+from vyos.util import call
+from vyos.util import DEVNULL
+from vyos.util import dict_search
+from vyos.xml import defaults
from vyos import ConfigError
-
from vyos import airbag
airbag.enable()
radius_config_file = "/etc/pam_radius_auth.conf"
-default_config_data = {
- 'deleted': False,
- 'add_users': [],
- 'del_users': [],
- 'radius_server': [],
- 'radius_source_address': '',
- 'radius_vrf': ''
-}
-
-
def get_local_users():
"""Return list of dynamically allocated users (see Debian Policy Manual)"""
local_users = []
@@ -57,211 +54,132 @@ def get_local_users():
def get_config(config=None):
- login = default_config_data
if config:
conf = config
else:
conf = Config()
- base_level = ['system', 'login']
-
- # We do not need to check if the nodes exist or not and bail out early
- # ... this would interrupt the following logic on determine which users
- # should be deleted and which users should stay.
- #
- # All fine so far!
-
- # Read in all local users and store to list
- for username in conf.list_nodes(base_level + ['user']):
- user = {
- 'name': username,
- 'password_plaintext': '',
- 'password_encrypted': '!',
- 'public_keys': [],
- 'full_name': '',
- 'home_dir': '/home/' + username,
- }
- conf.set_level(base_level + ['user', username])
-
- # Plaintext password
- if conf.exists(['authentication', 'plaintext-password']):
- user['password_plaintext'] = conf.return_value(
- ['authentication', 'plaintext-password'])
-
- # Encrypted password
- if conf.exists(['authentication', 'encrypted-password']):
- user['password_encrypted'] = conf.return_value(
- ['authentication', 'encrypted-password'])
-
- # User real name
- if conf.exists(['full-name']):
- user['full_name'] = conf.return_value(['full-name'])
-
- # User home-directory
- if conf.exists(['home-directory']):
- user['home_dir'] = conf.return_value(['home-directory'])
-
- # Read in public keys
- for id in conf.list_nodes(['authentication', 'public-keys']):
- key = {
- 'name': id,
- 'key': '',
- 'options': '',
- 'type': ''
- }
- conf.set_level(base_level + ['user', username, 'authentication',
- 'public-keys', id])
-
- # Public Key portion
- if conf.exists(['key']):
- key['key'] = conf.return_value(['key'])
-
- # Options for individual public key
- if conf.exists(['options']):
- key['options'] = conf.return_value(['options'])
-
- # Type of public key
- if conf.exists(['type']):
- key['type'] = conf.return_value(['type'])
-
- # Append individual public key to list of user keys
- user['public_keys'].append(key)
-
- login['add_users'].append(user)
-
- #
- # RADIUS configuration
- #
- conf.set_level(base_level + ['radius'])
-
- if conf.exists(['source-address']):
- login['radius_source_address'] = conf.return_value(['source-address'])
-
- # retrieve VRF instance
- if conf.exists(['vrf']):
- login['radius_vrf'] = conf.return_value(['vrf'])
-
- # Read in all RADIUS servers and store to list
- for server in conf.list_nodes(['server']):
- server_cfg = {
- 'address': server,
- 'disabled': False,
- 'key': '',
- 'port': '1812',
- 'timeout': '2',
- 'priority': 255
- }
- conf.set_level(base_level + ['radius', 'server', server])
-
- # Check if RADIUS server was temporary disabled
- if conf.exists(['disable']):
- server_cfg['disabled'] = True
-
- # RADIUS shared secret
- if conf.exists(['key']):
- server_cfg['key'] = conf.return_value(['key'])
-
- # RADIUS authentication port
- if conf.exists(['port']):
- server_cfg['port'] = conf.return_value(['port'])
-
- # RADIUS session timeout
- if conf.exists(['timeout']):
- server_cfg['timeout'] = conf.return_value(['timeout'])
-
- # Check if RADIUS server has priority
- if conf.exists(['priority']):
- server_cfg['priority'] = int(conf.return_value(['priority']))
-
- # Append individual RADIUS server configuration to global server list
- login['radius_server'].append(server_cfg)
+ base = ['system', 'login']
+ login = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
# users no longer existing in the running configuration need to be deleted
local_users = get_local_users()
- cli_users = [tmp['name'] for tmp in login['add_users']]
- # create a list of all users, cli and users
- all_users = list(set(local_users+cli_users))
+ cli_users = []
+ if 'user' in login:
+ cli_users = list(login['user'])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ default_values = defaults(base + ['user'])
+ for user in login['user']:
+ login['user'][user] = dict_merge(default_values, login['user'][user])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ default_values = defaults(base + ['radius', 'server'])
+ for server in dict_search('radius.server', login) or []:
+ login['radius']['server'][server] = dict_merge(default_values,
+ login['radius']['server'][server])
+
+ # XXX: for a yet unknown reason when we only have one source-address
+ # get_config_dict() will show a string over a string
+ if 'radius' in login and 'source_address' in login['radius']:
+ print(type(login['radius']['source_address']))
+ if isinstance(login['radius']['source_address'], str):
+ login['radius']['source_address'] = [login['radius']['source_address']]
- # Remove any normal users that dos not exist in the current configuration.
- # This can happen if user is added but configuration was not saved and
- # system is rebooted.
- login['del_users'] = [tmp for tmp in all_users if tmp not in cli_users]
+ # create a list of all users, cli and users
+ all_users = list(set(local_users + cli_users))
+ # We will remove any normal users that dos not exist in the current
+ # configuration. This can happen if user is added but configuration was not
+ # saved and system is rebooted.
+ rm_users = [tmp for tmp in all_users if tmp not in cli_users]
+ if rm_users: login.update({'rm_users' : rm_users})
return login
-
def verify(login):
- cur_user = os.environ['SUDO_USER']
- if cur_user in login['del_users']:
- raise ConfigError(
- 'Attempting to delete current user: {}'.format(cur_user))
-
- for user in login['add_users']:
- for key in user['public_keys']:
- if not key['type']:
- raise ConfigError(
- 'SSH public key type missing for "{name}"!'.format(**key))
-
- if not key['key']:
- raise ConfigError(
- 'SSH public key for id "{name}" missing!'.format(**key))
+ if 'rm_users' in login:
+ cur_user = os.environ['SUDO_USER']
+ if cur_user in login['rm_users']:
+ raise ConfigError(f'Attempting to delete current user: {cur_user}')
+
+ if 'user' in login:
+ for user, user_config in login['user'].items():
+ for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items():
+ if 'type' not in pubkey_options:
+ raise ConfigError(f'Missing type for public-key "{pubkey}"!')
+ if 'key' not in pubkey_options:
+ raise ConfigError(f'Missing key for public-key "{pubkey}"!')
# At lease one RADIUS server must not be disabled
- if len(login['radius_server']) > 0:
+ if 'radius' in login:
+ if 'server' not in login['radius']:
+ raise ConfigError('No RADIUS server defined!')
+
fail = True
- for server in login['radius_server']:
- if not server['disabled']:
+ for server, server_config in dict_search('radius.server', login).items():
+ if 'key' not in server_config:
+ raise ConfigError(f'RADIUS server "{server}" requires key!')
+
+ if 'disabled' not in server_config:
fail = False
+ continue
if fail:
- raise ConfigError('At least one RADIUS server must be active.')
+ raise ConfigError('All RADIUS servers are disabled')
+
+ verify_vrf(login['radius'])
+
+ if 'source_address' in login['radius']:
+ ipv4_count = 0
+ ipv6_count = 0
+ for address in login['radius']['source_address']:
+ if is_ipv4(address): ipv4_count += 1
+ else: ipv6_count += 1
- vrf_name = login['radius_vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if ipv4_count > 1:
+ raise ConfigError('Only one IPv4 source-address can be set!')
+ if ipv6_count > 1:
+ raise ConfigError('Only one IPv6 source-address can be set!')
return None
def generate(login):
# calculate users encrypted password
- for user in login['add_users']:
- if user['password_plaintext']:
- user['password_encrypted'] = crypt(
- user['password_plaintext'], METHOD_SHA512)
- user['password_plaintext'] = ''
-
- # remove old plaintext password and set new encrypted password
- env = os.environ.copy()
- env['vyos_libexec_dir'] = '/usr/libexec/vyos'
-
- call("/opt/vyatta/sbin/my_delete system login user '{name}' "
- "authentication plaintext-password"
- .format(**user), env=env)
-
- call("/opt/vyatta/sbin/my_set system login user '{name}' "
- "authentication encrypted-password '{password_encrypted}'"
- .format(**user), env=env)
-
- else:
- try:
- if getspnam(user['name']).sp_pwdp == user['password_encrypted']:
- # If the current encrypted bassword matches the encrypted password
- # from the config - do not update it. This will remove the encrypted
- # value from the system logs.
- #
- # The encrypted password will be set only once during the first boot
- # after an image upgrade.
- user['password_encrypted'] = ''
- except:
- pass
-
- if len(login['radius_server']) > 0:
- render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl',
- login)
-
- uid = getpwnam('root').pw_uid
- gid = getpwnam('root').pw_gid
- os.chown(radius_config_file, uid, gid)
- chmod_600(radius_config_file)
+ if 'user' in login:
+ for user, user_config in login['user'].items():
+ tmp = dict_search('authentication.plaintext_password', user_config)
+ if tmp:
+ encrypted_password = crypt(tmp, METHOD_SHA512)
+ login['user'][user]['authentication']['encrypted_password'] = encrypted_password
+ del login['user'][user]['authentication']['plaintext_password']
+
+ # remove old plaintext password and set new encrypted password
+ env = os.environ.copy()
+ env['vyos_libexec_dir'] = '/usr/libexec/vyos'
+
+ call(f"/opt/vyatta/sbin/my_delete system login user '{user}' "
+ "authentication plaintext-password", env=env)
+
+ call(f"/opt/vyatta/sbin/my_set system login user '{user}' "
+ "authentication encrypted-password '{encrypted_password}'", env=env)
+ else:
+ try:
+ if getspnam(user).sp_pwdp == dict_search('authentication.encrypted_password', user_config):
+ # If the current encrypted bassword matches the encrypted password
+ # from the config - do not update it. This will remove the encrypted
+ # value from the system logs.
+ #
+ # The encrypted password will be set only once during the first boot
+ # after an image upgrade.
+ del login['user'][user]['authentication']['encrypted_password']
+ except:
+ pass
+
+ if 'radius' in login:
+ render(radius_config_file, 'login/pam_radius_auth.conf.tmpl', login,
+ permission=0o600, user='root', group='root')
else:
if os.path.isfile(radius_config_file):
os.unlink(radius_config_file)
@@ -270,95 +188,72 @@ def generate(login):
def apply(login):
- for user in login['add_users']:
- # make new user using vyatta shell and make home directory (-m),
- # default group of 100 (users)
- command = "useradd -m -N"
- # check if user already exists:
- if user['name'] in get_local_users():
- # update existing account
- command = "usermod"
-
- # all accounts use /bin/vbash
- command += " -s /bin/vbash"
- # we need to use '' quotes when passing formatted data to the shell
- # else it will not work as some data parts are lost in translation
- if user['password_encrypted']:
- command += " -p '{}'".format(user['password_encrypted'])
-
- if user['full_name']:
- command += " -c '{}'".format(user['full_name'])
-
- if user['home_dir']:
- command += " -d '{}'".format(user['home_dir'])
-
- command += " -G frrvty,vyattacfg,sudo,adm,dip,disk"
- command += " {}".format(user['name'])
-
- try:
- cmd(command)
-
- uid = getpwnam(user['name']).pw_uid
- gid = getpwnam(user['name']).pw_gid
-
- # we should not rely on the value stored in user['home_dir'], as a
- # crazy user will choose username root or any other system user
- # which will fail. Should we deny using root at all?
- home_dir = getpwnam(user['name']).pw_dir
-
- # install ssh keys
- ssh_key_dir = home_dir + '/.ssh'
- if not os.path.isdir(ssh_key_dir):
- os.mkdir(ssh_key_dir)
- os.chown(ssh_key_dir, uid, gid)
- chmod_755(ssh_key_dir)
-
- ssh_key_file = ssh_key_dir + '/authorized_keys'
- with open(ssh_key_file, 'w') as f:
- f.write("# Automatically generated by VyOS\n")
- f.write("# Do not edit, all changes will be lost\n")
-
- for id in user['public_keys']:
- line = ''
- if id['options']:
- line = '{} '.format(id['options'])
-
- line += '{} {} {}\n'.format(id['type'],
- id['key'], id['name'])
- f.write(line)
-
- os.chown(ssh_key_file, uid, gid)
- chmod_600(ssh_key_file)
-
- except Exception as e:
- print(e)
- raise ConfigError('Adding user "{name}" raised exception'
- .format(**user))
-
- for user in login['del_users']:
- try:
- # Logout user if he is logged in
- if user in list(set([tmp[0] for tmp in users()])):
- print('{} is logged in, forcing logout'.format(user))
- call('pkill -HUP -u {}'.format(user))
-
- # Remove user account but leave home directory to be safe
- call(f'userdel -r {user}', stderr=DEVNULL)
-
- except Exception as e:
- raise ConfigError(f'Deleting user "{user}" raised exception: {e}')
+ if 'user' in login:
+ for user, user_config in login['user'].items():
+ # make new user using vyatta shell and make home directory (-m),
+ # default group of 100 (users)
+ command = 'useradd -m -N'
+ # check if user already exists:
+ if user in get_local_users():
+ # update existing account
+ command = 'usermod'
+
+ # all accounts use /bin/vbash
+ command += ' -s /bin/vbash'
+ # we need to use '' quotes when passing formatted data to the shell
+ # else it will not work as some data parts are lost in translation
+ tmp = dict_search('authentication.encrypted_password', user_config)
+ if tmp: command += f" -p '{tmp}'"
+
+ tmp = dict_search('full_name', user_config)
+ if tmp: command += f" -c '{tmp}'"
+
+ tmp = dict_search('home_directory', user_config)
+ if tmp: command += f" -d '{tmp}'"
+ else: command += f" -d '/home/{user}'"
+
+ command += f' -G frrvty,vyattacfg,sudo,adm,dip,disk {user}'
+
+ try:
+ cmd(command)
+
+ # we should not rely on the value stored in
+ # user_config['home_directory'], as a crazy user will choose
+ # username root or any other system user which will fail.
+ #
+ # XXX: Should we deny using root at all?
+ home_dir = getpwnam(user).pw_dir
+ render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.tmpl',
+ user_config, permission=0o600, user=user, group='users')
+
+ except Exception as e:
+ raise ConfigError(f'Adding user "{user}" raised exception: "{e}"')
+
+ if 'rm_users' in login:
+ for user in login['rm_users']:
+ try:
+ # Logout user if he is still logged in
+ if user in list(set([tmp[0] for tmp in users()])):
+ print(f'{user} is logged in, forcing logout!')
+ call(f'pkill -HUP -u {user}')
+
+ # Remove user account but leave home directory to be safe
+ call(f'userdel -r {user}', stderr=DEVNULL)
+
+ except Exception as e:
+ raise ConfigError(f'Deleting user "{user}" raised exception: {e}')
#
# RADIUS configuration
#
- if len(login['radius_server']) > 0:
- try:
- env = os.environ.copy()
- env['DEBIAN_FRONTEND'] = 'noninteractive'
+ env = os.environ.copy()
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
+ try:
+ if 'radius' in login:
# Enable RADIUS in PAM
- cmd("pam-auth-update --package --enable radius", env=env)
-
- # Make NSS system aware of RADIUS, too
+ cmd('pam-auth-update --package --enable radius', env=env)
+ # Make NSS system aware of RADIUS
+ # This fancy snipped was copied from old Vyatta code
command = "sed -i -e \'/\smapname/b\' \
-e \'/^passwd:/s/\s\s*/&mapuid /\' \
-e \'/^passwd:.*#/s/#.*/mapname &/\' \
@@ -366,31 +261,20 @@ def apply(login):
-e \'/^group:.*#/s/#.*/ mapname &/\' \
-e \'/^group:[^#]*$/s/: */&mapname /\' \
/etc/nsswitch.conf"
-
- cmd(command)
-
- except Exception as e:
- raise ConfigError('RADIUS configuration failed: {}'.format(e))
-
- else:
- try:
- env = os.environ.copy()
- env['DEBIAN_FRONTEND'] = 'noninteractive'
-
+ else:
# Disable RADIUS in PAM
- cmd("pam-auth-update --package --remove radius", env=env)
-
+ cmd('pam-auth-update --package --remove radius', env=env)
+ # Drop RADIUS from NSS NSS system
+ # This fancy snipped was copied from old Vyatta code
command = "sed -i -e \'/^passwd:.*mapuid[ \t]/s/mapuid[ \t]//\' \
-e \'/^passwd:.*[ \t]mapname/s/[ \t]mapname//\' \
-e \'/^group:.*[ \t]mapname/s/[ \t]mapname//\' \
-e \'s/[ \t]*$//\' \
/etc/nsswitch.conf"
- cmd(command)
-
- except Exception as e:
- raise ConfigError(
- 'Removing RADIUS configuration failed.\n{}'.format(e))
+ cmd(command)
+ except Exception as e:
+ raise ConfigError(f'RADIUS configuration failed: {e}')
return None