diff options
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | interface-definitions/system-login.xml.in | 176 | ||||
-rw-r--r-- | src/conf_mode/system-login.py | 145 |
3 files changed, 322 insertions, 0 deletions
diff --git a/debian/control b/debian/control index 9df421977..6e59ea2fb 100644 --- a/debian/control +++ b/debian/control @@ -63,6 +63,7 @@ Depends: python3, openvpn, openvpn-auth-ldap, openvpn-auth-radius, + libpam-radius-auth, mtr-tiny, telnet, traceroute, diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in new file mode 100644 index 000000000..33197d191 --- /dev/null +++ b/interface-definitions/system-login.xml.in @@ -0,0 +1,176 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="login" owner="${vyos_conf_scripts_dir}/system_login.py"> + <properties> + <help>User Login</help> + <priority>400</priority> + </properties> + <children> + <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> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>RADIUS shared secret key</help> + </properties> + </leafNode> + <leafNode name="port"> + <properties> + <help>RADIUS 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>Timeout for RADIUS session</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> + <tagNode name="user"> + <properties> + <help>User account information</help> + <constraint> + <regex>[a-zA-Z0-9\-_\.]{1,100}</regex> + </constraint> + <constraintErrorMessage>Username contains illegal characters or\nexceeds 100 character limitation.</constraintErrorMessage> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Password authentication</help> + </properties> + <children> + <leafNode name="encrypted-password"> + <properties> + <help>Encrypted password</help> + <constraint> + <regex>(\*|\!)</regex> + <regex>[a-zA-Z0-9\.\/]{13}$</regex> + <regex>\$1\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{22}$</regex> + <regex>\$5\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43}$</regex> + <regex>\$6\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86}$</regex> + </constraint> + <constraintErrorMessage>Invalid encrypted password for $VAR(../../@).</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="plaintext-password"> + <properties> + <help>Plaintext password used for encryption</help> + </properties> + </leafNode> + <tagNode name="public-keys"> + <properties> + <help>Remote access public keys</help> + <valueHelp> + <format>>identifier<</format> + <description>Key identifier used by ssh-keygen (usually of form user@host)</description> + </valueHelp> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>Public key value (base64-encoded)</help> + <completionHelp> + <script>echo 'The key is usually several hundred bytes long (because of the size of the public key encoding). Use the loadkey tool to input key from a URL or file.'</script> + </completionHelp> + </properties> + </leafNode> + <leafNode name="options"> + <properties> + <help>Optional public key options</help> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help></help> + <completionHelp> + <list>ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519</list> + </completionHelp> + <valueHelp> + <format>ssh-dss</format> + <description/> + </valueHelp> + <valueHelp> + <format>ssh-rsa</format> + <description/> + </valueHelp> + <valueHelp> + <format>ecdsa-sha2-nistp256</format> + <description/> + </valueHelp> + <valueHelp> + <format>ecdsa-sha2-nistp384</format> + <description/> + </valueHelp> + <valueHelp> + <format>ssh-ed25519</format> + <description/> + </valueHelp> + <constraint> + <regex>(ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519s)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="full-name"> + <properties> + <help>Full name of the user (use quotes for names with spaces)</help> + <constraint> + <regex>[^:]*$</regex> + </constraint> + <constraintErrorMessage>Cannot use ':' in full name</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="home-directory"> + <properties> + <help>Home directory</help> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py new file mode 100644 index 000000000..2c1e4dc3e --- /dev/null +++ b/src/conf_mode/system-login.py @@ -0,0 +1,145 @@ +#!/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 + +from vyos.config import Config +from vyos import ConfigError + +default_config_data = { + 'deleted': False, + 'radius_server': [], + 'radius_source': '', + 'user': [] +} + +def get_config(): + login = default_config_data + conf = Config() + base_level = ['system', 'login'] + + if not conf.exists(base_level): + login['deleted'] = True + return login + + if conf.exists(base_level + ['radius', 'source-address']): + login['radius_source'] = conf.return_value(['radius', 'source-address']) + + # Read in all RADIUS servers and store to list + for server in conf.list_nodes(base_level + ['radius', 'server']): + radius = { + 'address': server, + 'key': '', + 'port': '1812', + 'timeout': '2' + } + conf.set_level(base_level + ['radius', 'server', server]) + + # RADIUS shared secret + if conf.exists(['key']): + radius['key'] = conf.return_value(['key']) + + # RADIUS authentication port + if conf.exists(['port']): + radius['port'] = conf.return_value(['port']) + + # RADIUS session timeout + if conf.exists(['timeout']): + radius['timeout'] = conf.return_value(['timeout']) + + # Append individual RADIUS server configuration to global server list + login['radius_server'].append(radius) + + # 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']) + + # 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']): + user['key'] = conf.return_value(['key']) + + # Options for individual public key + if conf.exists(['options']): + user['options'] = conf.return_value(['options']) + + # Type of public key + if conf.exists(['type']): + user['type'] = conf.return_value(['type']) + + # Append individual public key to list of user keys + user['public_keys'].append(key) + + # set proper config level + conf.set_level(base_level + ['user', username]) + + # 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']) + + return login + +def verify(login): + pass + +def generate(login): + import pprint + pprint.pprint(login) + + pass + +def apply(login): + pass + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) |