summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface-definitions/system-login-radius.xml.in80
-rw-r--r--interface-definitions/system-login.xml.in (renamed from interface-definitions/system-login-user.xml.in)75
-rwxr-xr-xsrc/conf_mode/system-login-radius.py182
-rwxr-xr-xsrc/conf_mode/system-login.py (renamed from src/conf_mode/system-login-user.py)134
4 files changed, 200 insertions, 271 deletions
diff --git a/interface-definitions/system-login-radius.xml.in b/interface-definitions/system-login-radius.xml.in
deleted file mode 100644
index c5d081356..000000000
--- a/interface-definitions/system-login-radius.xml.in
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="system">
- <children>
- <node name="login">
- <children>
- <node name="radius" owner="${vyos_conf_scripts_dir}/system-login-radius.py">
- <properties>
- <help>RADIUS based user authentication</help>
- </properties>
- <children>
- <leafNode name="source-address">
- <properties>
- <help>RADIUS client source address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>TFTP IPv4 listen address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <tagNode name="server">
- <properties>
- <help>RADIUS server configuration</help>
- <valueHelp>
- <format>ipv4</format>
- <description>RADIUS server IPv4 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- <children>
- <leafNode name="disable">
- <properties>
- <help>Temporary disable this server</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="key">
- <properties>
- <help>Shared secret key</help>
- </properties>
- </leafNode>
- <leafNode name="port">
- <properties>
- <help>Authentication port</help>
- <valueHelp>
- <format>1-65535</format>
- <description>Numeric IP port (default: 1812)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="timeout">
- <properties>
- <help>Session timeout</help>
- <valueHelp>
- <format>1-30</format>
- <description>Session timeout in seconds (default: 2)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-30"/>
- </constraint>
- <constraintErrorMessage>Timeout must be between 1 and 30 seconds</constraintErrorMessage>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/system-login-user.xml.in b/interface-definitions/system-login.xml.in
index c183e93e3..fda567074 100644
--- a/interface-definitions/system-login-user.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -2,15 +2,15 @@
<interfaceDefinition>
<node name="system">
<children>
- <node name="login">
+ <node name="login" owner="${vyos_conf_scripts_dir}/system-login.py">
<properties>
<help>User Login</help>
<priority>400</priority>
</properties>
<children>
- <tagNode name="user" owner="${vyos_conf_scripts_dir}/system-login-user.py">
+ <tagNode name="user">
<properties>
- <help>User account information</help>
+ <help>Local user account information</help>
<constraint>
<regex>[a-zA-Z0-9\-_\.]{1,100}</regex>
</constraint>
@@ -110,6 +110,75 @@
</leafNode>
</children>
</tagNode>
+ <node name="radius">
+ <properties>
+ <help>RADIUS based user authentication</help>
+ </properties>
+ <children>
+ <leafNode name="source-address">
+ <properties>
+ <help>RADIUS client source address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>TFTP IPv4 listen address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <tagNode name="server">
+ <properties>
+ <help>RADIUS server configuration</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>RADIUS server IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Temporary disable this server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="key">
+ <properties>
+ <help>Shared secret key</help>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Authentication port</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port (default: 1812)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="timeout">
+ <properties>
+ <help>Session timeout</help>
+ <valueHelp>
+ <format>1-30</format>
+ <description>Session timeout in seconds (default: 2)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-30"/>
+ </constraint>
+ <constraintErrorMessage>Timeout must be between 1 and 30 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/src/conf_mode/system-login-radius.py b/src/conf_mode/system-login-radius.py
deleted file mode 100755
index b1e7dce4e..000000000
--- a/src/conf_mode/system-login-radius.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 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 sys
-import os
-import jinja2
-
-from pwd import getpwall, getpwnam
-from stat import S_IRUSR, S_IWUSR
-
-from vyos.config import Config
-from vyos.configdict import list_diff
-from vyos import ConfigError
-
-radius_config_file = "/etc/pam_radius_auth.conf"
-radius_config_tmpl = """
-# Automatically generated by VyOS
-# RADIUS configuration file
-{%- if server %}
-# server[:port] shared_secret timeout (s) source_ip
-{% for s in server %}
-{%- if not s.disabled -%}
-{{ s.address }}:{{ s.port }} {{ s.key }} {{ s.timeout }} {% if source_address -%}{{ source_address }}{% endif %}
-{% endif %}
-{%- endfor %}
-
-priv-lvl 15
-mapped_priv_user radius_priv_user
-{% endif %}
-
-"""
-
-default_config_data = {
- 'server': [],
- 'source_address': '',
-}
-
-def get_local_users():
- """Returns list of dynamically allocated users (see Debian Policy Manual)"""
- local_users = []
- for p in getpwall():
- username = p[0]
- uid = getpwnam(username).pw_uid
- if uid in range(1000, 29999):
- if username not in ['radius_user', 'radius_priv_user']:
- local_users.append(username)
-
- return local_users
-
-def get_config():
- radius = default_config_data
- conf = Config()
- base_level = ['system', 'login', 'radius']
-
- if not conf.exists(base_level):
- return radius
-
- conf.set_level(base_level)
-
- if conf.exists(['source-address']):
- radius['source_address'] = conf.return_value(['source-address'])
-
- # 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'
- }
- conf.set_level(base_level + ['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'])
-
- # Append individual RADIUS server configuration to global server list
- radius['server'].append(server_cfg)
-
- return radius
-
-def verify(radius):
- # At lease one RADIUS server must not be disabled
- if len(radius['server']) > 0:
- fail = True
- for server in radius['server']:
- if not server['disabled']:
- fail = False
- if fail:
- raise ConfigError('At least one RADIUS server must be active.')
-
- return None
-
-def generate(radius):
- if len(radius['server']) > 0:
- tmpl = jinja2.Template(radius_config_tmpl)
- config_text = tmpl.render(radius)
- with open(radius_config_file, 'w') as f:
- f.write(config_text)
-
- uid = getpwnam('root').pw_uid
- gid = getpwnam('root').pw_gid
- os.chown(radius_config_file, uid, gid)
- os.chmod(radius_config_file, S_IRUSR | S_IWUSR)
- else:
- os.unlink(radius_config_file)
-
- return None
-
-def apply(radius):
- if len(radius['server']) > 0:
- try:
- # Enable RADIUS in PAM
- os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --enable radius")
-
- # Make NSS system aware of RADIUS, too
- cmd = "sed -i -e \'/\smapname/b\' \
- -e \'/^passwd:/s/\s\s*/&mapuid /\' \
- -e \'/^passwd:.*#/s/#.*/mapname &/\' \
- -e \'/^passwd:[^#]*$/s/$/ mapname &/\' \
- -e \'/^group:.*#/s/#.*/ mapname &/\' \
- -e \'/^group:[^#]*$/s/: */&mapname /\' \
- /etc/nsswitch.conf"
-
- os.system(cmd)
-
- except Exception as e:
- raise ConfigError('RADIUS configuration failed: {}'.format(e))
-
- else:
- try:
- # Disable RADIUS in PAM
- os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --remove radius")
-
- cmd = "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"
-
- os.system(cmd)
-
- except Exception as e:
- raise ConfigError('Removing RADIUS configuration failed'.format(e))
-
- return None
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)
diff --git a/src/conf_mode/system-login-user.py b/src/conf_mode/system-login.py
index 087279dc7..23152fee0 100755
--- a/src/conf_mode/system-login-user.py
+++ b/src/conf_mode/system-login.py
@@ -16,6 +16,7 @@
import sys
import os
+import jinja2
from pwd import getpwall, getpwnam
from stat import S_IRUSR, S_IWUSR, S_IRWXU, S_IRGRP, S_IXGRP
@@ -26,10 +27,30 @@ from vyos.config import Config
from vyos.configdict import list_diff
from vyos import ConfigError
+radius_config_file = "/etc/pam_radius_auth.conf"
+radius_config_tmpl = """
+# Automatically generated by VyOS
+# RADIUS configuration file
+{%- if radius_server %}
+# server[:port] shared_secret timeout (s) source_ip
+{% for s in radius_server %}
+{%- if not s.disabled -%}
+{{ s.address }}:{{ s.port }} {{ s.key }} {{ s.timeout }} {% if radius_source_address -%}{{ radius_source_address }}{% endif %}
+{% endif %}
+{%- endfor %}
+
+priv-lvl 15
+mapped_priv_user radius_priv_user
+{% endif %}
+
+"""
+
default_config_data = {
'deleted': False,
'add_users': [],
- 'del_users': []
+ 'del_users': [],
+ 'radius_server': [],
+ 'radius_source_address': '',
}
def get_local_users():
@@ -55,7 +76,7 @@ def get_crypt_pw(password):
def get_config():
login = default_config_data
conf = Config()
- base_level = ['system', 'login', 'user']
+ 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
@@ -64,7 +85,7 @@ def get_config():
# All fine so far!
# Read in all local users and store to list
- for username in conf.list_nodes(base_level):
+ for username in conf.list_nodes(base_level + ['user']):
user = {
'name': username,
'password_plaintext': '',
@@ -73,7 +94,7 @@ def get_config():
'full_name': '',
'home_dir': '/home/' + username,
}
- conf.set_level(base_level + [username])
+ conf.set_level(base_level + ['user', username])
# Plaintext password
if conf.exists(['authentication', 'plaintext-password']):
@@ -99,7 +120,7 @@ def get_config():
'options': '',
'type': ''
}
- conf.set_level(base_level + [username, 'authentication', 'public-keys', id])
+ conf.set_level(base_level + ['user', username, 'authentication', 'public-keys', id])
# Public Key portion
if conf.exists(['key']):
@@ -118,6 +139,44 @@ def get_config():
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'])
+
+ # 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'
+ }
+ 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'])
+
+ # Append individual RADIUS server configuration to global server list
+ login['radius_server'].append(server_cfg)
+
# 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']]
@@ -129,6 +188,7 @@ def get_config():
# system is rebooted.
login['del_users'] = [tmp for tmp in all_users if tmp not in cli_users]
+
return login
def verify(login):
@@ -136,7 +196,17 @@ def verify(login):
if cur_user in login['del_users']:
raise ConfigError('Attempting to delete current user: {}'.format(cur_user))
- pass
+ # At lease one RADIUS server must not be disabled
+ if len(login['radius_server']) > 0:
+ fail = True
+ for server in login['radius_server']:
+ if not server['disabled']:
+ fail = False
+ if fail:
+ raise ConfigError('At least one RADIUS server must be active.')
+
+
+ return None
def generate(login):
# calculate users encrypted password
@@ -150,6 +220,20 @@ def generate(login):
os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name']))
os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted']))
+ if len(login['radius_server']) > 0:
+ tmpl = jinja2.Template(radius_config_tmpl)
+ config_text = tmpl.render(login)
+ with open(radius_config_file, 'w') as f:
+ f.write(config_text)
+
+ uid = getpwnam('root').pw_uid
+ gid = getpwnam('root').pw_gid
+ os.chown(radius_config_file, uid, gid)
+ os.chmod(radius_config_file, S_IRUSR | S_IWUSR)
+ else:
+ if os.path.isfile(radius_config_file):
+ os.unlink(radius_config_file)
+
return None
def apply(login):
@@ -220,6 +304,44 @@ def apply(login):
except Exception as e:
raise ConfigError('Deleting user "{}" raised an exception: {}'.format(user, e))
+ #
+ # RADIUS configuration
+ #
+ if len(login['radius_server']) > 0:
+ try:
+ # Enable RADIUS in PAM
+ os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --enable radius")
+
+ # Make NSS system aware of RADIUS, too
+ cmd = "sed -i -e \'/\smapname/b\' \
+ -e \'/^passwd:/s/\s\s*/&mapuid /\' \
+ -e \'/^passwd:.*#/s/#.*/mapname &/\' \
+ -e \'/^passwd:[^#]*$/s/$/ mapname &/\' \
+ -e \'/^group:.*#/s/#.*/ mapname &/\' \
+ -e \'/^group:[^#]*$/s/: */&mapname /\' \
+ /etc/nsswitch.conf"
+
+ os.system(cmd)
+
+ except Exception as e:
+ raise ConfigError('RADIUS configuration failed: {}'.format(e))
+
+ else:
+ try:
+ # Disable RADIUS in PAM
+ os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --remove radius")
+
+ cmd = "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"
+
+ os.system(cmd)
+
+ except Exception as e:
+ raise ConfigError('Removing RADIUS configuration failed'.format(e))
+
return None
if __name__ == '__main__':