Skip to content

Commit

Permalink
ldap: Add LDAP unittests in hostcfgd
Browse files Browse the repository at this point in the history
  • Loading branch information
davidpil2002 committed Dec 3, 2023
1 parent 1272100 commit 360c770
Show file tree
Hide file tree
Showing 16 changed files with 867 additions and 0 deletions.
9 changes: 9 additions & 0 deletions data/templates/common-auth-sonic.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions data/templates/ldap.conf.j2
Original file line number Diff line number Diff line change
@@ -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) }}
41 changes: 41 additions & 0 deletions data/templates/nslcd.conf.j2
Original file line number Diff line number Diff line change
@@ -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

116 changes: 116 additions & 0 deletions tests/hostcfgd/hostcfgd_ldap_test.py
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 21 additions & 0 deletions tests/hostcfgd/sample_output/LDAP/common-auth-sonic
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 21 additions & 0 deletions tests/hostcfgd/sample_output/LDAP/ldap.conf
Original file line number Diff line number Diff line change
@@ -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
116 changes: 116 additions & 0 deletions tests/hostcfgd/sample_output/LDAP/login
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 360c770

Please sign in to comment.