diff --git a/data/templates/common-auth-sonic.j2 b/data/templates/common-auth-sonic.j2 index b20c9f4e..fa68e613 100644 --- a/data/templates/common-auth-sonic.j2 +++ b/data/templates/common-auth-sonic.j2 @@ -68,6 +68,15 @@ auth [success=2 default=ignore] pam_exec.so /usr/sbin/cache_radius # Local auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass +{% elif auth['login'] == 'ldap,local' %} +auth [success=2 default=ignore] pam_ldap.so minimum_uid=1000 try_first_pass +auth [success=1 default=ignore] pam_unix.so nullok try_first_pass +{% elif auth['login'] == 'local,ldap' %} +auth [success=2 default=ignore] pam_unix.so nullok try_first_pass +auth [success=1 default=ignore] pam_ldap.so minimum_uid=1000 try_first_pass +{% elif auth['login'] == 'ldap' %} +auth [success=1 default=ignore] pam_ldap.so minimum_uid=1000 try_first_pass + {% else %} auth [success=1 default=ignore] pam_unix.so nullok try_first_pass diff --git a/data/templates/ldap.conf.j2 b/data/templates/ldap.conf.j2 new file mode 100644 index 00000000..c059b9a9 --- /dev/null +++ b/data/templates/ldap.conf.j2 @@ -0,0 +1,17 @@ +{{ ldap_cfg.cfg_servers(servers) }} + +base {{ ldap_cfg.cfg_base(servers) }} + +ldap_version {{ ldap_cfg.cfg_version(servers) }} + +binddn {{ ldap_cfg.cfg_bind(servers) }} + +bindpw {{ ldap_cfg.cfg_bindpw(servers) }} + +port {{ ldap_cfg.cfg_port(servers) }} + +scope {{ ldap_cfg.cfg_scope(servers) }} + +timelimit {{ ldap_cfg.cfg_timeout(servers) }} + +bind_timelimit {{ ldap_cfg.cfg_bind_timeout(servers) }} diff --git a/data/templates/nslcd.conf.j2 b/data/templates/nslcd.conf.j2 new file mode 100644 index 00000000..f5d2b6c8 --- /dev/null +++ b/data/templates/nslcd.conf.j2 @@ -0,0 +1,41 @@ +# /etc/nslcd.conf +# nslcd configuration file. See nslcd.conf(5) +# for details. + +# The user and group nslcd should run as. +uid nslcd +gid nslcd + +# The location at which the LDAP server(s) should be reachable. +{{ ldap_cfg.cfg_servers(servers) }} + +# The search base that will be used for all queries. +base {{ ldap_cfg.cfg_base(servers) }} + + +# The LDAP protocol version to use. +ldap_version {{ ldap_cfg.cfg_version(servers) }} + +# The DN to bind with for normal lookups. +binddn {{ ldap_cfg.cfg_bind(servers) }} +bindpw {{ ldap_cfg.cfg_bindpw(servers) }} + +# The DN used for password modifications by root. +#rootpwmoddn cn=admin,dc=example,dc=com + +# SSL options +#ssl off +#tls_reqcert never +tls_cacertfile /etc/ssl/certs/ca-certificates.crt + +# The search scope. +scope {{ ldap_cfg.cfg_scope(servers) }} + +timelimit {{ ldap_cfg.cfg_timeout(servers) }} + +bind_timelimit {{ ldap_cfg.cfg_bind_timeout(servers) }} + +nss_initgroups_ignoreusers ALLLOCAL + +nss_min_uid 1000 + diff --git a/scripts/ldap.py b/scripts/ldap.py index f7b71901..8a5dc21b 100644 --- a/scripts/ldap.py +++ b/scripts/ldap.py @@ -15,18 +15,7 @@ class LdapCfg: PORT = 389 SCOPE = "sub" HOST = "" - HOSTNAME_CHECK = "no" - GROUP_BASE_DN = "ou=users,dc=example,dc=com" - GROUP_MEMBER_ATTR = "member" IPV6 = 6 - SSL_MODE = "none" - CERT_VERIFY = "try" # tls_reqcert never|allow|try|demand|hard - # Folder for CA certs - SSL_CACERT_FILE = "none" - SSL_CIPHERS = "all" - # CRL check is not implemented in current version of nslcd - SSL_CRL_CHECK = "none" - SSL_CRL_FILE = "default" @staticmethod def _do_cfg(_ldapsrvs_conf, attr, cfg_str): @@ -53,10 +42,6 @@ def cfg_servers(_ldapsrvs_conf): servers_resp = f"uri " ldap_mode = "ldap" port = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.PORT, 'port') - ssl_mode = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.PORT, 'ssl_mode') - if ssl_mode == 'ssl': - port = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.PORT, 'ssl_port') - ldap_mode = "ldaps" for server in _ldapsrvs_conf: ip = server.get('ip', LdapCfg.HOST) try: @@ -93,57 +78,3 @@ def cfg_timeout(_ldapsrvs_conf): @staticmethod def cfg_bind_timeout(_ldapsrvs_conf): return LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.TIMEOUT_BIND, 'bind_timeout') - - @staticmethod - def cfg_ssl_mode(_ldapsrvs_conf): - ssl_mode = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.SSL_MODE, 'ssl_mode') - ssl_modes = { - "start-tls": "start_tls", - "ssl": "on", - "none": "off" - } - ret_ssl_mode = ssl_modes.get(ssl_mode, "") - return ret_ssl_mode - - @staticmethod - def cfg_tls_reqcert(_ldapsrvs_conf): - ssl_mode = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.SSL_MODE, 'ssl_mode') - cert_verify_ret = "" - # cert verify is only active in case ssl or start-tls are active - if ssl_mode == 'ssl' or ssl_mode == 'start-tls': - cert_verify_db = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.CERT_VERIFY, 'cert_verify') - if cert_verify_db == "enabled": - cert_verify_ret = "tls_reqcert demand" - elif cert_verify_db == "disabled": - cert_verify_ret = "tls_reqcert never" - else: - syslog.syslog(syslog.LOG_WARNING, f"Cert verify contains an invalid value: {cert_verify_db}") - else: - cert_verify_ret = "" - return cert_verify_ret - - @staticmethod - def cfg_ca_certfile(_ldapsrvs_conf): - default_ca_list = "/etc/ssl/certs/ca-certificates.crt" - ca_list = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.SSL_CACERT_FILE, 'ca_list') - # Use default ca -list file. And add certificate manaement API to get list of certtificates - if ca_list == 'none': - ca_list_ret = "" - else: - ca_list_ret = f"tls_cacertfile {default_ca_list}" - return ca_list_ret - - @staticmethod - def cfg_tls_ciphers(_ldapsrvs_conf): - tls_ciphers_ret = "" - tls_ciphers = LdapCfg._do_cfg(_ldapsrvs_conf, LdapCfg.SSL_CIPHERS, 'tls_ciphers') - if tls_ciphers == "all": - tls_ciphers_ret = "" - elif tls_ciphers == "TLS1.2": - tls_ciphers_ret = f"tls_ciphers {TLS1_2}" - elif tls_ciphers == "TLS1.3": - tls_ciphers_ret = f"tls_ciphers {TLS1_3}" - else: - tls_ciphers_ret = "" - syslog.syslog(syslog.LOG_ERR, f"LDAP TLS cipher contains an invalid value:: {tls_ciphers}") - return tls_ciphers_ret diff --git a/tests/hostcfgd/hostcfgd_ldap_test.py b/tests/hostcfgd/hostcfgd_ldap_test.py new file mode 100644 index 00000000..89fbaffa --- /dev/null +++ b/tests/hostcfgd/hostcfgd_ldap_test.py @@ -0,0 +1,116 @@ +import importlib.machinery +import importlib.util +import filecmp +import shutil +import os +import sys +from swsscommon import swsscommon + +from parameterized import parameterized +from unittest import TestCase, mock +from tests.hostcfgd.test_ldap_vectors import HOSTCFGD_TEST_LDAP_VECTOR +from tests.common.mock_configdb import MockConfigDb, MockDBConnector +from sonic_py_common.general import getstatusoutput_noshell + + +test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +modules_path = os.path.dirname(test_path) +scripts_path = os.path.join(modules_path, "scripts") +templates_path = os.path.join(modules_path, "data/templates") +output_path = os.path.join(test_path, "hostcfgd/output") +sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") +sys.path.insert(0, modules_path) + +# Load the file under test +hostcfgd_path = os.path.join(scripts_path, 'hostcfgd') +loader = importlib.machinery.SourceFileLoader('hostcfgd', hostcfgd_path) +spec = importlib.util.spec_from_loader(loader.name, loader) +hostcfgd = importlib.util.module_from_spec(spec) +loader.exec_module(hostcfgd) +sys.modules['hostcfgd'] = hostcfgd + +# Mock swsscommon classes +hostcfgd.ConfigDBConnector = MockConfigDb +hostcfgd.DBConnector = MockDBConnector +hostcfgd.Table = mock.Mock() + +class TestHostcfgdLDAP(TestCase): + """ + Test hostcfd daemon - LDAP + """ + def run_diff(self, file1, file2): + _, output = getstatusoutput_noshell(['diff', '-uR', file1, file2]) + return output + + + @parameterized.expand(HOSTCFGD_TEST_LDAP_VECTOR) + def test_hostcfgd_ldap(self, test_name, test_data): + """ + Test LDAP hostcfd daemon initialization + Args: + test_name(str): test name + test_data(dict): test data which contains initial Config Db tables, and expected results + Returns: + None + """ + + t_path = templates_path + op_path = output_path + "/" + test_name + sop_path = sample_output_path + "/" + test_name + + hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" + hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" + hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" + hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" + hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" + hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" + hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" + hostcfgd.NSS_CONF = op_path + "/nsswitch.conf" + hostcfgd.NSLCD_CONF = op_path + "/nslcd.conf" + hostcfgd.NSLCD_CONF_TEMPLATE = t_path + "/nslcd.conf.j2" + hostcfgd.ETC_PAMD_SSHD = op_path + "/sshd" + hostcfgd.ETC_PAMD_LOGIN = op_path + "/login" + hostcfgd.RADIUS_PAM_AUTH_CONF_DIR = op_path + "/" + + shutil.rmtree( op_path, ignore_errors=True) + os.mkdir( op_path) + + shutil.copyfile( sop_path + "/sshd.old", op_path + "/sshd") + shutil.copyfile( sop_path + "/login.old", op_path + "/login") + + MockConfigDb.set_config_db(test_data["config_db"]) + host_config_daemon = hostcfgd.HostConfigDaemon() + + aaa = host_config_daemon.config_db.get_table('AAA') + + try: + ldap_global = host_config_daemon.config_db.get_table('LDAP') + except: + ldap_global = [] + try: + ldap_server = \ + host_config_daemon.config_db.get_table('LDAP_SERVER') + except: + ldap_server = [] + + host_config_daemon.aaacfg.load(aaa,[],[],[] ,[] , ldap_global, ldap_server) + + diff_output = "" + files_to_compare = ['common-auth-sonic', 'nslcd.conf'] + + # check output files exists + for name in files_to_compare: + if not os.path.isfile(sop_path + "/" + name): + raise ValueError('filename: %s not exit' % (sop_path + "/" + name)) + if not os.path.isfile(op_path + "/" + name): + raise ValueError('filename: %s not exit' % (op_path + "/" + name)) + + # deep comparison + match, mismatch, errors = filecmp.cmpfiles(sop_path, op_path, files_to_compare, shallow=False) + + if not match: + for name in files_to_compare: + diff_output += self.run_diff( sop_path + "/" + name,\ + op_path + "/" + name).decode('utf-8') + + self.assertTrue(len(diff_output) == 0, diff_output) diff --git a/tests/hostcfgd/sample_output/LDAP/common-auth-sonic b/tests/hostcfgd/sample_output/LDAP/common-auth-sonic new file mode 100644 index 00000000..848565cd --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/common-auth-sonic @@ -0,0 +1,21 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-auth- authentication settings common to all services +# This file is included from other service-specific PAM config files, +# and should contain a list of the authentication modules that define +# the central authentication scheme for use on the system +# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the +# traditional Unix authentication mechanisms. +# +# here are the per-package modules (the "Primary" block) + +auth [success=2 default=ignore] pam_ldap.so minimum_uid=1000 try_first_pass +auth [success=1 default=ignore] pam_unix.so nullok try_first_pass +# +# here's the fallback if no module succeeds +auth requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +auth required pam_permit.so +# and here are more per-package modules (the "Additional" block) diff --git a/tests/hostcfgd/sample_output/LDAP/ldap.conf b/tests/hostcfgd/sample_output/LDAP/ldap.conf new file mode 100644 index 00000000..d0fe09fb --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/ldap.conf @@ -0,0 +1,21 @@ +uri ldap://10.10.10.2/ +uri ldap://10.10.10.1/ + + +base ou=users,dc=example,dc=com + +ldap_version 3 + +binddn + +bindpw pass + +port 389 + +scope sub + +timelimit 3 + +bind_timelimit 5 + +pam_check_host_attr no \ No newline at end of file diff --git a/tests/hostcfgd/sample_output/LDAP/login b/tests/hostcfgd/sample_output/LDAP/login new file mode 100644 index 00000000..80ba6452 --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/login @@ -0,0 +1,116 @@ +# +# The PAM configuration file for the Shadow `login' service +# + +# Enforce a minimal delay in case of failure (in microseconds). +# (Replaces the `FAIL_DELAY' setting from login.defs) +# Note that other modules may require another minimal delay. (for example, +# to disable any delay, you should add the nodelay option to pam_unix) +auth optional pam_faildelay.so delay=3000000 + +# Outputs an issue file prior to each login prompt (Replaces the +# ISSUE_FILE option from login.defs). Uncomment for use +# auth required pam_issue.so issue=/etc/issue + +# Disallows root logins except on tty's listed in /etc/securetty +# (Replaces the `CONSOLE' setting from login.defs) +# +# With the default control of this module: +# [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] +# root will not be prompted for a password on insecure lines. +# if an invalid username is entered, a password is prompted (but login +# will eventually be rejected) +# +# You can change it to a "requisite" module if you think root may mis-type +# her login and should not be prompted for a password in that case. But +# this will leave the system as vulnerable to user enumeration attacks. +# +# You can change it to a "required" module if you think it permits to +# guess valid user names of your system (invalid user names are considered +# as possibly being root on insecure lines), but root passwords may be +# communicated over insecure lines. +auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible +# that a module could execute code in the wrong domain. +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Sets the loginuid process attribute +session required pam_loginuid.so + +# SELinux needs to intervene at login time to ensure that the process +# starts in the proper default security context. Only sessions which are +# intended to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) + +# This module parses environment configuration file(s) +# and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# +# parsing /etc/environment needs "readenv=1" +session required pam_env.so readenv=1 +# locale variables are also kept into /etc/default/locale in etch +# reading this file *in addition to /etc/environment* does not hurt +session required pam_env.so readenv=1 envfile=/etc/default/locale + +# Standard Un*x authentication. +@include common-auth-sonic + +# This allows certain extra groups to be granted to a user +# based on things like time of day, tty, service, and user. +# Please edit /etc/security/group.conf to fit your needs +# (Replaces the `CONSOLE_GROUPS' option in login.defs) +auth optional pam_group.so + +# Uncomment and edit /etc/security/time.conf if you need to set +# time restraint on logins. +# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs +# as well as /etc/porttime) +# account requisite pam_time.so + +# Uncomment and edit /etc/security/access.conf if you need to +# set access limits. +# (Replaces /etc/login.access file) +# account required pam_access.so + +# Sets up user limits according to /etc/security/limits.conf +# (Replaces the use of /etc/limits in old login) +session required pam_limits.so + +# Prints the last login info upon successful login +# (Replaces the `LASTLOG_ENAB' option from login.defs) +session optional pam_lastlog.so + +# Prints the message of the day upon successful login. +# (Replaces the `MOTD_FILE' option in login.defs) +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Prints the status of the user's mailbox upon successful login +# (Replaces the `MAIL_CHECK_ENAB' option from login.defs). +# +# This also defines the MAIL environment variable +# However, userdel also needs MAIL_DIR and MAIL_FILE variables +# in /etc/login.defs to make sure that removing a user +# also removes the user's mail spool file. +# See comments in /etc/login.defs +session optional pam_mail.so standard + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x account and session +@include common-account +@include common-session +@include common-password diff --git a/tests/hostcfgd/sample_output/LDAP/login.old b/tests/hostcfgd/sample_output/LDAP/login.old new file mode 100644 index 00000000..07ff9540 --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/login.old @@ -0,0 +1,116 @@ +# +# The PAM configuration file for the Shadow `login' service +# + +# Enforce a minimal delay in case of failure (in microseconds). +# (Replaces the `FAIL_DELAY' setting from login.defs) +# Note that other modules may require another minimal delay. (for example, +# to disable any delay, you should add the nodelay option to pam_unix) +auth optional pam_faildelay.so delay=3000000 + +# Outputs an issue file prior to each login prompt (Replaces the +# ISSUE_FILE option from login.defs). Uncomment for use +# auth required pam_issue.so issue=/etc/issue + +# Disallows root logins except on tty's listed in /etc/securetty +# (Replaces the `CONSOLE' setting from login.defs) +# +# With the default control of this module: +# [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] +# root will not be prompted for a password on insecure lines. +# if an invalid username is entered, a password is prompted (but login +# will eventually be rejected) +# +# You can change it to a "requisite" module if you think root may mis-type +# her login and should not be prompted for a password in that case. But +# this will leave the system as vulnerable to user enumeration attacks. +# +# You can change it to a "required" module if you think it permits to +# guess valid user names of your system (invalid user names are considered +# as possibly being root on insecure lines), but root passwords may be +# communicated over insecure lines. +auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible +# that a module could execute code in the wrong domain. +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Sets the loginuid process attribute +session required pam_loginuid.so + +# SELinux needs to intervene at login time to ensure that the process +# starts in the proper default security context. Only sessions which are +# intended to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) + +# This module parses environment configuration file(s) +# and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# +# parsing /etc/environment needs "readenv=1" +session required pam_env.so readenv=1 +# locale variables are also kept into /etc/default/locale in etch +# reading this file *in addition to /etc/environment* does not hurt +session required pam_env.so readenv=1 envfile=/etc/default/locale + +# Standard Un*x authentication. +@include common-auth + +# This allows certain extra groups to be granted to a user +# based on things like time of day, tty, service, and user. +# Please edit /etc/security/group.conf to fit your needs +# (Replaces the `CONSOLE_GROUPS' option in login.defs) +auth optional pam_group.so + +# Uncomment and edit /etc/security/time.conf if you need to set +# time restraint on logins. +# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs +# as well as /etc/porttime) +# account requisite pam_time.so + +# Uncomment and edit /etc/security/access.conf if you need to +# set access limits. +# (Replaces /etc/login.access file) +# account required pam_access.so + +# Sets up user limits according to /etc/security/limits.conf +# (Replaces the use of /etc/limits in old login) +session required pam_limits.so + +# Prints the last login info upon successful login +# (Replaces the `LASTLOG_ENAB' option from login.defs) +session optional pam_lastlog.so + +# Prints the message of the day upon successful login. +# (Replaces the `MOTD_FILE' option in login.defs) +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Prints the status of the user's mailbox upon successful login +# (Replaces the `MAIL_CHECK_ENAB' option from login.defs). +# +# This also defines the MAIL environment variable +# However, userdel also needs MAIL_DIR and MAIL_FILE variables +# in /etc/login.defs to make sure that removing a user +# also removes the user's mail spool file. +# See comments in /etc/login.defs +session optional pam_mail.so standard + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x account and session +@include common-account +@include common-session +@include common-password diff --git a/tests/hostcfgd/sample_output/LDAP/nslcd.conf b/tests/hostcfgd/sample_output/LDAP/nslcd.conf new file mode 100644 index 00000000..bc303b63 --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/nslcd.conf @@ -0,0 +1,40 @@ +# /etc/nslcd.conf +# nslcd configuration file. See nslcd.conf(5) +# for details. + +# The user and group nslcd should run as. +uid nslcd +gid nslcd + +# The location at which the LDAP server(s) should be reachable. +uri ldap://10.10.10.2:389/ ldap://10.10.10.1:389/ + +# The search base that will be used for all queries. +base ou=users,dc=example,dc=com + + +# The LDAP protocol version to use. +ldap_version 3 + +# The DN to bind with for normal lookups. +binddn cn=ldapadm,dc=example,dc=com +bindpw pass + +# The DN used for password modifications by root. +#rootpwmoddn cn=admin,dc=example,dc=com + +# SSL options +#ssl off +#tls_reqcert never +tls_cacertfile /etc/ssl/certs/ca-certificates.crt + +# The search scope. +scope sub + +timelimit 2 + +bind_timelimit 2 + +nss_initgroups_ignoreusers ALLLOCAL + +nss_min_uid 1000 diff --git a/tests/hostcfgd/sample_output/LDAP/nsswitch.conf b/tests/hostcfgd/sample_output/LDAP/nsswitch.conf new file mode 100644 index 00000000..d7365584 --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/nsswitch.conf @@ -0,0 +1,18 @@ +mple configuration of GNU Name Service Switch functionality. +# If you have the `glibc-doc-reference' and `info' packages installed, try: +# `info libc "Name Service Switch"' for information about this file. + +passwd: files ldap +group: files ldap +shadow: files ldap +gshadow: files + +hosts: files dns +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis \ No newline at end of file diff --git a/tests/hostcfgd/sample_output/LDAP/nsswitch.conf.old b/tests/hostcfgd/sample_output/LDAP/nsswitch.conf.old new file mode 100644 index 00000000..660d438c --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/nsswitch.conf.old @@ -0,0 +1,18 @@ +mple configuration of GNU Name Service Switch functionality. +# If you have the `glibc-doc-reference' and `info' packages installed, try: +# `info libc "Name Service Switch"' for information about this file. + +passwd: files ldap +group: files ldap +shadow: files +gshadow: files + +hosts: files dns +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis \ No newline at end of file diff --git a/tests/hostcfgd/sample_output/LDAP/radius_nss.conf b/tests/hostcfgd/sample_output/LDAP/radius_nss.conf new file mode 100644 index 00000000..8c31db9f --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/radius_nss.conf @@ -0,0 +1,56 @@ +#THIS IS AN AUTO-GENERATED FILE +# Generated from: /usr/share/sonic/templates/radius_nss.conf.j2 +# RADIUS NSS Configuration File +# +# Debug: on|off|trace +# Default: off +# +# debug=on +debug=on + +# +# User Privilege: +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell + +# Eg: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=7;pw_info=netops;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=operator;gid=100;group=docker;shell=/usr/bin/sonic-launch-shell +# + +# many_to_one: +# y: Map RADIUS users to one local user per privilege. +# n: Create local user account on first successful authentication. +# Default: n +# + +# Eg: +# many_to_one=y +# + +# unconfirmed_disallow: +# y: Do not allow unconfirmed users (users created before authentication) +# n: Allow unconfirmed users. +# Default: n + +# Eg: +# unconfirmed_disallow=y +# + +# unconfirmed_ageout: +# : Wait time before purging unconfirmed users +# Default: 600 +# + +# Eg: +# unconfirmed_ageout=900 +# + +# unconfirmed_regexp: +# : The RE to match the command line of processes for which the +# creation of unconfirmed users are to be allowed. +# Default: (.*: \[priv\])|(.*: \[accepted\]) +# where: is the unconfirmed user. +# \ No newline at end of file diff --git a/tests/hostcfgd/sample_output/LDAP/sshd b/tests/hostcfgd/sample_output/LDAP/sshd new file mode 100644 index 00000000..c025af35 --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/sshd @@ -0,0 +1,55 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +@include common-auth-sonic + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so user_readenv=1 envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/tests/hostcfgd/sample_output/LDAP/sshd.old b/tests/hostcfgd/sample_output/LDAP/sshd.old new file mode 100644 index 00000000..d70b384b --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/sshd.old @@ -0,0 +1,55 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +@include common-auth + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so user_readenv=1 envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/tests/hostcfgd/sample_output/LDAP/tacplus_nss.conf b/tests/hostcfgd/sample_output/LDAP/tacplus_nss.conf new file mode 100644 index 00000000..eac82849 --- /dev/null +++ b/tests/hostcfgd/sample_output/LDAP/tacplus_nss.conf @@ -0,0 +1,40 @@ +# Configuration for libnss-tacplus + +# debug - If you want to open debug log, set it on +# Default: off +# debug=on +debug=on + +# local_accounting - If you want to local accounting, set it +# Default: None +# local_accounting + +# tacacs_accounting - If you want to tacacs+ accounting, set it +# Default: None +# tacacs_accounting + +# local_authorization - If you want to local authorization, set it +# Default: None +# local_authorization +local_authorization + +# tacacs_authorization - If you want to tacacs+ authorization, set it +# Default: None +# tacacs_authorization + +# src_ip - set source address of TACACS+ protocol packets +# Default: None (auto source ip address) +# src_ip=2.2.2.2 + +# server - set ip address, tcp port, secret string and timeout for TACACS+ servers +# Default: None (no TACACS+ server) +# server=1.1.1.1:49,secret=test,timeout=3 + +# user_priv - set the map between TACACS+ user privilege and local user's passwd +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/bin/bash +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/bin/bash + +# many_to_one - create one local user for many TACACS+ users which has the same privilege +# Default: many_to_one=n +# many_to_one=y diff --git a/tests/hostcfgd/test_ldap_vectors.py b/tests/hostcfgd/test_ldap_vectors.py new file mode 100644 index 00000000..1026dcbb --- /dev/null +++ b/tests/hostcfgd/test_ldap_vectors.py @@ -0,0 +1,128 @@ +from unittest.mock import call + +""" + hostcfgd test ldap vector +""" +HOSTCFGD_TEST_LDAP_VECTOR = [ + [ + "LDAP", + { + "config_db": { + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "ldap,local", + "restrictions":{ + "lockout-state": "enabled", + "fail-delay": 0, + "lockout-reattempt": 15, + "lockout-attempts": 5 + }, + "failthrough": "True", + "debug": "True", + } + }, + "LDAP": { + "global": { + "port": "389", + "bind_password": "pass", + "bind_dn": "cn=ldapadm,dc=example,dc=com", + "base_dn": "ou=users,dc=example,dc=com", + "bind_timeout": "2", + "search_timeout": "2", + "version": "3" + } + }, + "LDAP_SERVER": { + "10.10.10.1": { + "priority": "1" + }, + "10.10.10.2": { + "priority": "2" + } + }, + "SSH_SERVER": { + "POLICIES" :{ + "max_sessions": "100" + } + } + }, + "expected_config_db": { + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "AAA": { + "authentication": { + "login": "ldap,local", + "restrictions":{ + "lockout-state": "enabled", + "fail-delay": 0, + "lockout-reattempt": 15, + "lockout-attempts": 5 + }, + "debug": "True", + } + }, + "LDAP": { + "global": { + "auth_port": "389", + "timeout": "3", + "passkey": "pass", + } + }, + "LDAP_SERVER": { + "10.10.10.1": { + "priority": "1", + "passkey": "pass1", + }, + "10.10.10.2": { + "priority": "2", + } + }, + "SSH_SERVER": { + "POLICIES" :{ + "max_sessions": "100" + } + } + }, + "expected_subprocess_calls": [ + call("service aaastatsd start", shell=True), + ], + } + ] +]