diff options
| -rw-r--r-- | data/templates/login/nsswitch.conf.j2 | 21 | ||||
| -rw-r--r-- | data/templates/login/tacplus_nss.conf.j2 | 74 | ||||
| -rw-r--r-- | data/templates/login/tacplus_servers.j2 | 59 | ||||
| -rw-r--r-- | debian/vyos-1x.postinst | 46 | ||||
| -rw-r--r-- | debian/vyos-1x.preinst | 1 | ||||
| -rw-r--r-- | interface-definitions/system-login.xml.in | 59 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_system_login.py | 41 | ||||
| -rwxr-xr-x | src/conf_mode/system-login.py | 141 | ||||
| -rw-r--r-- | src/pam-configs/tacplus | 17 | 
9 files changed, 396 insertions, 63 deletions
| diff --git a/data/templates/login/nsswitch.conf.j2 b/data/templates/login/nsswitch.conf.j2 new file mode 100644 index 000000000..65dc88291 --- /dev/null +++ b/data/templates/login/nsswitch.conf.j2 @@ -0,0 +1,21 @@ +# Automatically generated by system-login.py +# /etc/nsswitch.conf +# +# Example configuration of GNU Name Service Switch functionality. + +passwd:         {{ 'mapuid ' if radius is vyos_defined }}{{ 'tacplus ' if tacacs is vyos_defined }}files{{ ' mapname' if radius is vyos_defined }} +group:          {{ 'mapname ' if radius is vyos_defined }}{{ 'tacplus ' if tacacs is vyos_defined }}files +shadow:         files +gshadow:        files + +# Per T2678, commenting out myhostname +hosts:          files dns #myhostname +networks:       files + +protocols:      db files +services:       db files +ethers:         db files +rpc:            db files + +netgroup:       nis + diff --git a/data/templates/login/tacplus_nss.conf.j2 b/data/templates/login/tacplus_nss.conf.j2 new file mode 100644 index 000000000..2a30b1710 --- /dev/null +++ b/data/templates/login/tacplus_nss.conf.j2 @@ -0,0 +1,74 @@ +#%NSS_TACPLUS-1.0 +# Install this file as /etc/tacplus_nss.conf +# Edit /etc/nsswitch.conf to add tacplus to the passwd lookup, similar to this +# where tacplus precede compat (or files), and depending on local policy can +# follow or precede ldap, nis, etc. +#    passwd: tacplus compat +# +#  Servers are tried in the order listed, and once a server +#  replies, no other servers are attempted in a given process instantiation +# +#  This configuration is similar to the libpam_tacplus configuration, but +#  is maintained as a configuration file, since nsswitch.conf doesn't +#  support passing parameters.  Parameters must start in the first +#  column, and parsing stops at the first whitespace + +# if set, errors and other issues are logged with syslog +#debug=1 + +# min_uid is the minimum uid to lookup via tacacs.  Setting this to 0 +# means uid 0 (root) is never looked up, good for robustness and performance +# Cumulus Linux ships with it set to 1001, so we never lookup our standard +# local users, including the cumulus uid of 1000.  Should not be greater +# than the local tacacs{0..15} uids +min_uid=900 + +# This is a comma separated list of usernames that are never sent to +# a tacacs server, they cause an early not found return. +# +# "*" is not a wild card.  While it's not a legal username, it turns out +# that during pathname completion, bash can do an NSS lookup on "*" +# To avoid server round trip delays, or worse, unreachable server delays +# on filename completion, we include "*" in the exclusion list. +exclude_users=root,telegraf,radvd,strongswan,tftp,conservr,frr,ocserv,pdns,_chrony,_lldpd,sshd,openvpn,radius_user,radius_priv_user,*{{ ',' + user | join(',') if user is vyos_defined }} + +# The include keyword allows centralizing the tacacs+ server information +# including the IP address and shared secret +# include=/etc/tacplus_servers + +#  The server IP address can be optionally followed by a ':' and a port +#  number (server=1.1.1.1:49).  It is strongly recommended that you NOT +#  add secret keys to this file, because it is world readable. +{% if tacacs.server is vyos_defined %} +{%     for server, server_config in tacacs.server.items() %} +secret={{ server_config.key }} +server={{ server }}:{{ server_config.port }} + +{%     endfor %} +{% endif %} + +{% if tacacs.vrf is vyos_defined %} +# If the management network is in a vrf, set this variable to the vrf name. +# This would usually be "mgmt". When this variable is set, the connection to the +# TACACS+ accounting servers will be made through the named vrf. +vrf={{ tacacs.vrf }} +{% endif %} + +{% if tacacs.source_address is vyos_defined %} +# Sets the IPv4 address used as the source IP address when communicating with +# the TACACS+ server. IPv6 addresses are not supported, nor are hostnames. +# The address must work when passsed to the bind() system call, that is, it must +# be valid for the interface being used. +source_ip={{ tacacs.source_address }} +{% endif %} + +# The connection timeout for an NSS library should be short, since it is +# invoked for many programs and daemons, and a failure is usually not +# catastrophic.  Not set or set to a negative value disables use of poll(). +# This follows the include of tacplus_servers, so it can override any +# timeout value set in that file. +# It's important to have this set in this file, even if the same value +# as in tacplus_servers, since tacplus_servers should not be readable +# by users other than root. +timeout={{ tacacs.timeout }} + diff --git a/data/templates/login/tacplus_servers.j2 b/data/templates/login/tacplus_servers.j2 new file mode 100644 index 000000000..5a65d6e68 --- /dev/null +++ b/data/templates/login/tacplus_servers.j2 @@ -0,0 +1,59 @@ +# Automatically generated by system-login.py +# TACACS+ configuration file + +# This is a common file used by audisp-tacplus, libpam_tacplus, and +# libtacplus_map config files as shipped. +# +# Any tac_plus client config can go here that is common to all users of this +# file, but typically it's just the TACACS+ server IP address(es) and shared +# secret(s) +# +# This file should normally be mode 600, if you care about the security of your +# secret key. When set to mode 600 NSS lookups for TACACS users will only work +# for tacacs users that are logged in, via the local mapping. For root, lookups +# will work for any tacacs users, logged in or not. + +# Set a per-connection timeout of 10 seconds, and enable the use of poll() when +# trying to read from tacacs servers. Otherwise standard TCP timeouts apply. +# Not set or set to a negative value disables use of poll(). There are usually +# multiple connection attempts per login. +timeout={{ tacacs.timeout }} + +{% if tacacs.server is vyos_defined %} +{%     for server, server_config in tacacs.server.items() %} +secret={{ server_config.key }} +server={{ server }}:{{ server_config.port }} +{%     endfor %} +{% endif %} + +# If set, login/logout accounting records are sent to all servers in +# the list, otherwise only to the first responding server +# Also used by audisp-tacplus per-command accounting, if it sources this file. +acct_all=1 + +{% if tacacs.vrf is vyos_defined %} +# If the management network is in a vrf, set this variable to the vrf name. +# This would usually be "mgmt". When this variable is set, the connection to the +# TACACS+ accounting servers will be made through the named vrf. +vrf={{ tacacs.vrf }} +{% endif %} + +{% if tacacs.source_address is vyos_defined %} +# Sets the IPv4 address used as the source IP address when communicating with +# the TACACS+ server. IPv6 addresses are not supported, nor are hostnames. +# The address must work when passsed to the bind() system call, that is, it must +# be valid for the interface being used. +source_ip={{ tacacs.source_address }} +{% endif %} + +# If user_homedir=1, then tacacs users will be set to have a home directory +# based on their login name, rather than the mapped tacacsN home directory. +# mkhomedir_helper is used to create the directory if it does not exist (similar +# to use of pam_mkhomedir.so). This flag is ignored for users with restricted +# shells, e.g., users mapped to a tacacs privilege level that has enforced +# per-command authorization (see the tacplus-restrict man page). +user_homedir=1 + +service=shell +protocol=ssh + diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index a844cba65..9822ce286 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -29,6 +29,52 @@ do      sed -i "/^# Standard Un\*x authentication\./i${PAM_CONFIG}" $file  done +# Remove TACACS user added by base package - we use our own UID range and group +# assignments - see below +if grep -q '^tacacs' /etc/passwd; then +    if [ $(id -u tacacs0) -ge 1000 ]; then +        level=0 +        vyos_group=vyattaop +        while [ $level -lt 16 ]; do +            userdel tacacs${level} || true +            level=$(( level+1 )) +        done 2>&1 +    fi +fi + +# Add TACACS system users required for TACACS based system authentication +if ! grep -q '^tacacs' /etc/passwd; then +    # Add the tacacs group and all 16 possible tacacs privilege-level users to +    # the password file, home directories, etc. The accounts are not enabled +    # for local login, since they are only used to provide uid/gid/homedir for +    # the mapped TACACS+ logins (and lookups against them). The tacacs15 user +    # is also added to the sudo group, and vyattacfg group rather than vyattaop +    # (used for tacacs0-14). +    level=0 +    vyos_group=vyattaop +    while [ $level -lt 16 ]; do +        adduser --quiet --system --firstuid 900 --disabled-login --ingroup ${vyos_group} \ +            --no-create-home --gecos "TACACS+ mapped user at privilege level ${level}" \ +            --shell /bin/vbash tacacs${level} +        adduser --quiet tacacs${level} frrvty +        adduser --quiet tacacs${level} adm +        adduser --quiet tacacs${level} dip +        adduser --quiet tacacs${level} users +        adduser --quiet tacacs${level} aaa +        if [ $level -lt 15 ]; then +            adduser --quiet tacacs${level} vyattaop +            adduser --quiet tacacs${level} operator +        else +            adduser --quiet tacacs${level} vyattacfg +            adduser --quiet tacacs${level} sudo +            adduser --quiet tacacs${level} disk +            adduser --quiet tacacs${level} frr +        fi +        level=$(( level+1 )) +    done 2>&1 | grep -v 'User tacacs${level} already exists' +fi + +  if ! grep -q '^aaa' /etc/group; then      addgroup --firstgid 1000 --quiet aaa  fi diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst index 949ffcbc4..bfbeb112c 100644 --- a/debian/vyos-1x.preinst +++ b/debian/vyos-1x.preinst @@ -3,6 +3,7 @@ dpkg-divert --package vyos-1x --add --no-rename /etc/security/capability.conf  dpkg-divert --package vyos-1x --add --no-rename /lib/systemd/system/lcdproc.service  dpkg-divert --package vyos-1x --add --no-rename /etc/logrotate.d/conntrackd  dpkg-divert --package vyos-1x --add --no-rename /usr/share/pam-configs/radius +dpkg-divert --package vyos-1x --add --no-rename /usr/share/pam-configs/tacplus  dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.conf  dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.bashrc  dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index be4f53c3b..d772c7821 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -193,20 +193,7 @@              <children>                <tagNode name="server">                  <children> -                  <leafNode name="timeout"> -                    <properties> -                      <help>Session timeout</help> -                      <valueHelp> -                        <format>u32:1-30</format> -                        <description>Session timeout in seconds</description> -                      </valueHelp> -                      <constraint> -                        <validator name="numeric" argument="--range 1-30"/> -                      </constraint> -                      <constraintErrorMessage>Timeout must be between 1 and 30 seconds</constraintErrorMessage> -                    </properties> -                    <defaultValue>2</defaultValue> -                  </leafNode> +                  #include <include/radius-timeout.xml.i>                    <leafNode name="priority">                      <properties>                        <help>Server priority</help> @@ -225,6 +212,50 @@                #include <include/interface/vrf.xml.i>              </children>            </node> +          <node name="tacacs"> +            <properties> +              <help>TACACS+ based user authentication</help> +            </properties> +            <children> +              <tagNode name="server"> +                <properties> +                  <help>TACACS+ server configuration</help> +                  <valueHelp> +                    <format>ipv4</format> +                    <description>TACACS+ server IPv4 address</description> +                  </valueHelp> +                  <constraint> +                    <validator name="ipv4-address"/> +                  </constraint> +                </properties> +                <children> +                  #include <include/generic-disable-node.xml.i> +                  #include <include/radius-server-key.xml.i> +                  #include <include/port-number.xml.i> +                  <leafNode name="port"> +                    <defaultValue>49</defaultValue> +                  </leafNode> +                </children> +              </tagNode> +              <leafNode name="source-address"> +                <properties> +                  <help>Source IP used to communicate with TACACS+ server</help> +                  <completionHelp> +                    <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> +                  </completionHelp> +                  <valueHelp> +                    <format>ipv4</format> +                    <description>IPv4 source address</description> +                  </valueHelp> +                  <constraint> +                    <validator name="ipv4-address"/> +                  </constraint> +                </properties> +              </leafNode> +              #include <include/radius-timeout.xml.i> +              #include <include/interface/vrf.xml.i> +            </children> +          </node>            <leafNode name="max-login-session">              <properties>                <help>Maximum number of all login sessions</help> diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 1182cb1fc..8a4f5fdd1 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -17,11 +17,13 @@  import re  import platform  import unittest +import paramiko  from base_vyostest_shim import VyOSUnitTestSHIM  from subprocess import Popen, PIPE  from pwd import getpwall +from time import sleep  from vyos.configsession import ConfigSessionError  from vyos.util import cmd @@ -51,12 +53,16 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase):          # ensure we can also run this test on a live system - so lets clean          # out the current configuration which will break this test          cls.cli_delete(cls, base_path + ['radius']) +        cls.cli_delete(cls, base_path + ['tacacs'])      def tearDown(self):          # Delete individual users from configuration          for user in users:              self.cli_delete(base_path + ['user', user]) +        self.cli_delete(base_path + ['radius']) +        self.cli_delete(base_path + ['tacacs']) +          self.cli_commit()          # After deletion, a user is not allowed to remain in /etc/passwd @@ -279,6 +285,41 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase):          self.cli_delete(base_path + ['timeout'])          self.cli_delete(base_path + ['max-login-session']) +    def test_system_login_tacacs(self): +        tacacs_secret = 'tac_plus_key' +        tacacs_servers = ['100.64.0.11', '100.64.0.12'] + +        # Enable TACACS +        for server in tacacs_servers: +            self.cli_set(base_path + ['tacacs', 'server', server, 'key', tacacs_secret]) + +        self.cli_commit() + +        # NSS +        nsswitch_conf = read_file('/etc/nsswitch.conf') +        tmp = re.findall(r'passwd:\s+tacplus\s+files', nsswitch_conf) +        self.assertTrue(tmp) + +        tmp = re.findall(r'group:\s+tacplus\s+files', nsswitch_conf) +        self.assertTrue(tmp) + +        # PAM TACACS configuration +        pam_tacacs_conf = read_file('/etc/tacplus_servers') +        # NSS TACACS configuration +        nss_tacacs_conf = read_file('/etc/tacplus_nss.conf') +        # Users have individual home directories +        self.assertIn('user_homedir=1', pam_tacacs_conf) + +        # specify services +        self.assertIn('service=shell', pam_tacacs_conf) +        self.assertIn('protocol=ssh', pam_tacacs_conf) + +        for server in tacacs_servers: +            self.assertIn(f'secret={tacacs_secret}', pam_tacacs_conf) +            self.assertIn(f'server={server}', pam_tacacs_conf) + +            self.assertIn(f'secret={tacacs_secret}', nss_tacacs_conf) +            self.assertIn(f'server={server}', nss_tacacs_conf)  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index aefab8401..5f8dd17cd 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -30,7 +30,8 @@ from vyos.defaults import directories  from vyos.template import render  from vyos.template import is_ipv4  from vyos.util import cmd -from vyos.util import call, rc_cmd +from vyos.util import call +from vyos.util import rc_cmd  from vyos.util import run  from vyos.util import DEVNULL  from vyos.util import dict_search @@ -42,20 +43,34 @@ airbag.enable()  autologout_file = "/etc/profile.d/autologout.sh"  limits_file = "/etc/security/limits.d/10-vyos.conf"  radius_config_file = "/etc/pam_radius_auth.conf" +tacacs_pam_config_file = "/etc/tacplus_servers" +tacacs_nss_config_file = "/etc/tacplus_nss.conf" +nss_config_file = "/etc/nsswitch.conf" +# Minimum UID used when adding system users +MIN_USER_UID: int = 1000  # LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec  MAX_RADIUS_TIMEOUT: int = 50  # MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout)  MAX_RADIUS_COUNT: int = 25 +# Maximum number of supported TACACS servers +MAX_TACACS_COUNT: int = 8 + +# List of local user accounts that must be preserved +SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1', +                              'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6', +                              'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11', +                              'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15']  def get_local_users():      """Return list of dynamically allocated users (see Debian Policy Manual)"""      local_users = []      for s_user in getpwall(): -        uid = getpwnam(s_user.pw_name).pw_uid -        if uid in range(1000, 29999): -            if s_user.pw_name not in ['radius_user', 'radius_priv_user']: -                local_users.append(s_user.pw_name) +        if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID: +            continue +        if s_user.pw_name in SYSTEM_USER_SKIP_LIST: +            continue +        local_users.append(s_user.pw_name)      return local_users @@ -88,12 +103,21 @@ def get_config(config=None):          for user in login['user']:              login['user'][user] = dict_merge(default_values, login['user'][user]) +    # Add TACACS global defaults +    if 'tacacs' in login: +        default_values = defaults(base + ['tacacs']) +        if 'server' in default_values: +            del default_values['server'] +        login['tacacs'] = dict_merge(default_values, login['tacacs']) +      # 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]) +    for backend in ['radius', 'tacacs']: +        default_values = defaults(base + [backend, 'server']) +        for server in dict_search(f'{backend}.server', login) or []: +            login[backend]['server'][server] = dict_merge(default_values, +                login[backend]['server'][server]) +      # create a list of all users, cli and users      all_users = list(set(local_users + cli_users)) @@ -121,7 +145,7 @@ def verify(login):              # Linux system users range up until UID 1000, we can not create a              # VyOS CLI user which already exists as system user              for s_user in system_users: -                if s_user.pw_name == user and s_user.pw_uid < 1000: +                if s_user.pw_name == user and s_user.pw_uid < MIN_USER_UID:                      raise ConfigError(f'User "{user}" can not be created, conflict with local system account!')              for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items(): @@ -130,6 +154,9 @@ def verify(login):                  if 'key' not in pubkey_options:                      raise ConfigError(f'Missing key for public-key "{pubkey}"!') +    if {'radius', 'tacacs'} <= set(login): +        raise ConfigError('Using both RADIUS and TACACS at the same time is not supported!') +      # At lease one RADIUS server must not be disabled      if 'radius' in login:          if 'server' not in login['radius']: @@ -149,7 +176,7 @@ def verify(login):              raise ConfigError('All RADIUS servers are disabled')          if radius_servers_count > MAX_RADIUS_COUNT: -            raise ConfigError('Number of RADIUS servers more than 25 ') +            raise ConfigError(f'Number of RADIUS servers exceeded maximum of {MAX_RADIUS_COUNT}!')          if sum_timeout > MAX_RADIUS_TIMEOUT:              raise ConfigError('Sum of RADIUS servers timeouts ' @@ -169,6 +196,24 @@ def verify(login):              if ipv6_count > 1:                  raise ConfigError('Only one IPv6 source-address can be set!') +    if 'tacacs' in login: +        tacacs_servers_count: int = 0 +        fail = True +        for server, server_config in dict_search('tacacs.server', login).items(): +            if 'key' not in server_config: +                raise ConfigError(f'TACACS server "{server}" requires key!') +            if 'disable' not in server_config: +                tacacs_servers_count += 1 +                fail = False + +        if fail: +            raise ConfigError('All RADIUS servers are disabled') + +        if tacacs_servers_count > MAX_TACACS_COUNT: +            raise ConfigError(f'Number of TACACS servers exceeded maximum of {MAX_TACACS_COUNT}!') + +        verify_vrf(login['tacacs']) +      if 'max_login_session' in login and 'timeout' not in login:          raise ConfigError('"login timeout" must be configured!') @@ -190,8 +235,8 @@ def generate(login):                  env['vyos_libexec_dir'] = directories['base']                  # Set default commands for re-adding user with encrypted password -                del_user_plain = f"system login user '{user}' authentication plaintext-password" -                add_user_encrypt = f"system login user '{user}' authentication encrypted-password '{encrypted_password}'" +                del_user_plain = f"system login user {user} authentication plaintext-password" +                add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'"                  lvl = env['VYATTA_EDIT_LEVEL']                  # We're in config edit level, for example "edit system login" @@ -210,10 +255,10 @@ def generate(login):                      add_user_encrypt = add_user_encrypt[len(lvl):]                      add_user_encrypt = " ".join(add_user_encrypt) -                call(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env) +                ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env) +                if ret: raise ConfigError(out)                  ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env) -                if ret: -                    raise ConfigError(out) +                if ret: raise ConfigError(out)              else:                  try:                      if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config): @@ -227,6 +272,7 @@ def generate(login):                  except:                      pass +    ### RADIUS based user authentication      if 'radius' in login:          render(radius_config_file, 'login/pam_radius_auth.conf.j2', login,                     permission=0o600, user='root', group='root') @@ -234,6 +280,24 @@ def generate(login):          if os.path.isfile(radius_config_file):              os.unlink(radius_config_file) +    ### TACACS+ based user authentication +    if 'tacacs' in login: +        render(tacacs_pam_config_file, 'login/tacplus_servers.j2', login, +                   permission=0o644, user='root', group='root') +        render(tacacs_nss_config_file, 'login/tacplus_nss.conf.j2', login, +                   permission=0o644, user='root', group='root') +    else: +        if os.path.isfile(tacacs_pam_config_file): +            os.unlink(tacacs_pam_config_file) +        if os.path.isfile(tacacs_nss_config_file): +            os.unlink(tacacs_nss_config_file) + + + +    # NSS must always be present on the system +    render(nss_config_file, 'login/nsswitch.conf.j2', login, +               permission=0o644, user='root', group='root') +      # /etc/security/limits.d/10-vyos.conf      if 'max_login_session' in login:          render(limits_file, 'login/limits.j2', login, @@ -257,7 +321,7 @@ def apply(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 --create-home --no-user-group' +            command = 'useradd --create-home --no-user-group '              # check if user already exists:              if user in get_local_users():                  # update existing account @@ -327,38 +391,17 @@ def apply(login):              except Exception as e:                  raise ConfigError(f'Deleting user "{user}" raised exception: {e}') -    # -    # RADIUS configuration -    # -    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 -            # 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 &/\' \ -                          -e \'/^passwd:[^#]*$/s/$/ mapname &/\' \ -                          -e \'/^group:.*#/s/#.*/ mapname &/\' \ -                          -e \'/^group:[^#]*$/s/: */&mapname /\' \ -                          /etc/nsswitch.conf" -        else: -            # Disable RADIUS in PAM -            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(f'RADIUS configuration failed: {e}') +    # Enable RADIUS in PAM configuration +    pam_cmd = '--remove' +    if 'radius' in login: +        pam_cmd = '--enable' +    cmd(f'pam-auth-update --package {pam_cmd} radius') + +    # Enable/Disable TACACS in PAM configuration +    pam_cmd = '--remove' +    if 'tacacs' in login: +        pam_cmd = '--enable' +    cmd(f'pam-auth-update --package {pam_cmd} tacplus')      return None diff --git a/src/pam-configs/tacplus b/src/pam-configs/tacplus new file mode 100644 index 000000000..66a1eaa4c --- /dev/null +++ b/src/pam-configs/tacplus @@ -0,0 +1,17 @@ +Name: TACACS+ authentication +Default: no +Priority: 257 +Auth-Type: Primary +Auth: +    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet +    [authinfo_unavail=ignore success=end auth_err=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login + +Account-Type: Primary +Account: +    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet +    [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login + +Session-Type: Additional +Session: +    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet +    [authinfo_unavail=ignore success=ok default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login | 
