Skip to content

Commit

Permalink
[cli-sessions]
Browse files Browse the repository at this point in the history
 update hostcfgd: add serial-console and extend ssh-server modules,
 update existing tests,
 introduce serial-console test
  • Loading branch information
i-davydenko committed Jan 10, 2024
1 parent 722b796 commit a239567
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 13 deletions.
4 changes: 4 additions & 0 deletions data/templates/limits.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@
# ftp - chroot /ftp
# @student - maxlogins 4

{% if max_sessions and max_sessions | int > 0 -%}
* - maxsyslogins {{ max_sessions }}
{% endif -%}

# End of file
91 changes: 84 additions & 7 deletions scripts/hostcfgd
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ LINUX_DEFAULT_PASS_MAX_DAYS = 99999
LINUX_DEFAULT_PASS_WARN_AGE = 7

# Ssh min-max values
SSH_MIN_VALUES={"authentication_retries": 3, "login_timeout": 1, "ports": 1}
SSH_MAX_VALUES={"authentication_retries": 100, "login_timeout": 600, "ports": 65535}
SSH_CONFIG_NAMES={"authentication_retries": "MaxAuthTries" , "login_timeout": "LoginGraceTime"}
SSH_MIN_VALUES={"authentication_retries": 3, "login_timeout": 1, "ports": 1,
"inactivity_timeout": 0, "max_sessions": 0}
SSH_MAX_VALUES={"authentication_retries": 100, "login_timeout": 600,
"ports": 65535, "inactivity_timeout": 35000,
"max_sessions": 100}
SSH_CONFIG_NAMES={"authentication_retries": "MaxAuthTries",
"login_timeout": "LoginGraceTime", "ports": "Port",
"inactivity_timeout": "ClientAliveInterval"}

ACCOUNT_NAME = 0 # index of account name
AGE_DICT = { 'MAX_DAYS': {'REGEX_DAYS': r'^PASS_MAX_DAYS[ \t]*(?P<max_days>\d*)', 'DAYS': 'max_days', 'CHAGE_FLAG': '-M '},
Expand Down Expand Up @@ -911,9 +916,15 @@ class SshServer(object):
syslog.syslog(syslog.LOG_ERR, "Ssh {} {} out of range".format(key, value))
elif key in SSH_CONFIG_NAMES:
# search replace configuration - if not in config file - append
if key == "inactivity_timeout":
# translate min to sec.
value = int(value) * 60
kv_str = "{} {}".format(SSH_CONFIG_NAMES[key], str(value)) # name +' '+ value format
modify_single_file_inplace(SSH_CONFG_TMP,['-E', "/^#?" + SSH_CONFIG_NAMES[key]+"/{h;s/.*/"+
kv_str + "/};${x;/^$/{s//" + kv_str + "/;H};x}"])
elif key in ['max_sessions']:
# Ignore, these parameters handled in other modules
continue
else:
syslog.syslog(syslog.LOG_ERR, "Failed to update sshd config file - wrong key {}".format(key))

Expand Down Expand Up @@ -1128,16 +1139,31 @@ class PamLimitsCfg(object):
self.config_db = config_db
self.hwsku = ""
self.type = ""
self.max_sessions = None

# Load config from ConfigDb and render config file/
def update_config_file(self):
device_metadata = self.config_db.get_table('DEVICE_METADATA')
if "localhost" not in device_metadata:
ssh_server_policies = {}
try:
ssh_server_policies = self.config_db.get_table('SSH_SERVER')
except KeyError:
"""Dont throw except in case we don`t have SSH_SERVER config."""
pass

if "localhost" not in device_metadata and "POLICIES" not in ssh_server_policies:
return

self.read_localhost_config(device_metadata["localhost"])
self.read_max_sessions_config(ssh_server_policies.get("POLICIES", None))
self.render_conf_file()

# Read max_sessions config
def read_max_sessions_config(self, ssh_server_policies):
if ssh_server_policies is not None:
max_sess_cfg = ssh_server_policies.get('max_sessions', 0)
self.max_sessions = max_sess_cfg if max_sess_cfg != 0 else None

# Read localhost config
def read_localhost_config(self, localhost):
if "hwsku" in localhost:
Expand All @@ -1154,7 +1180,6 @@ class PamLimitsCfg(object):
def render_conf_file(self):
env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True)
env.filters['sub'] = sub

try:
template_file = os.path.abspath(PAM_LIMITS_CONF_TEMPLATE)
template = env.get_template(template_file)
Expand All @@ -1168,7 +1193,8 @@ class PamLimitsCfg(object):
template = env.get_template(template_file)
limits_conf = template.render(
hwsku=self.hwsku,
type=self.type)
type=self.type,
max_sessions=self.max_sessions)
with open(LIMITS_CONF, 'w') as f:
f.write(limits_conf)
except Exception as e:
Expand Down Expand Up @@ -1500,6 +1526,44 @@ class FipsCfg(object):
syslog.syslog(syslog.LOG_INFO, f'FipsCfg: update the FIPS enforce option {self.enforce}.')
loader.set_fips(image, self.enforce)


class SerialConsoleCfg:

def __init__(self):
self.cache = {}

def load(self, cli_sessions_conf):
self.cache = cli_sessions_conf or {}
syslog.syslog(syslog.LOG_INFO,
f'SerialConsoleCfg: Initial config: {self.cache}')

def update_serial_console_cfg(self, key, data):
'''
Apply config flow:
inactivity_timeout | set here AND in ssh_config flow | serial-config.service restarted.
max_sessions | set by PamLimitsCfg | serial-config.service DOESNT restarted.
sysrq_capabilities | set here | serial-config.service restarted.
'''

# max_sessions applied in limits.conf by PamLimitsCfg
if key in ['max_sessions']:
syslog.syslog(syslog.LOG_DEBUG,
f'SerialConsoleCfg: skip max_sessions in SerialConsoleCfg apply config handler')
return

if self.cache.get(key, {}) != data:
''' Config changed, need to restart the serial-config.service '''
syslog.syslog(syslog.LOG_INFO, f'Set serial-config parameter {key} value: {data}')
try:
run_cmd('sudo service serial-config restart', True, True)
except Exception:
syslog.syslog(syslog.LOG_ERR, f'Failed to update {key} serial-config.service config')
return
self.cache.update({key: data})

return


class HostConfigDaemon:
def __init__(self):
self.state_db_conn = DBConnector(STATE_DB, 0)
Expand Down Expand Up @@ -1551,6 +1615,9 @@ class HostConfigDaemon:
# Initialize FipsCfg
self.fipscfg = FipsCfg(self.state_db_conn)

# Initialize SerialConsoleCfg
self.serialconscfg = SerialConsoleCfg()

def load(self, init_data):
aaa = init_data['AAA']
tacacs_global = init_data['TACPLUS']
Expand All @@ -1571,6 +1638,7 @@ class HostConfigDaemon:
ntp_global = init_data.get(swsscommon.CFG_NTP_GLOBAL_TABLE_NAME)
ntp_servers = init_data.get(swsscommon.CFG_NTP_SERVER_TABLE_NAME)
ntp_keys = init_data.get(swsscommon.CFG_NTP_KEY_TABLE_NAME)
serial_console = init_data.get('SERIAL_CONSOLE', {})

self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server)
self.iptables.load(lpbk_table)
Expand All @@ -1579,11 +1647,12 @@ class HostConfigDaemon:
self.sshscfg.load(ssh_server)
self.devmetacfg.load(dev_meta)
self.mgmtifacecfg.load(mgmt_ifc, mgmt_vrf)

self.rsyslogcfg.load(syslog_cfg, syslog_srv)
self.dnscfg.load(dns)
self.fipscfg.load(fips_cfg)
self.ntpcfg.load(ntp_global, ntp_servers, ntp_keys)
self.serialconscfg.load(serial_console)
self.pamLimitsCfg.update_config_file()

# Update AAA with the hostname
self.aaacfg.hostname_update(self.devmetacfg.hostname)
Expand All @@ -1605,6 +1674,8 @@ class HostConfigDaemon:

def ssh_handler(self, key, op, data):
self.sshscfg.policies_update(key, data)
self.pamLimitsCfg.update_config_file()

syslog.syslog(syslog.LOG_INFO, 'SSH Update: key: {}, op: {}, data: {}'.format(key, op, data))

def tacacs_server_handler(self, key, op, data):
Expand Down Expand Up @@ -1716,6 +1787,10 @@ class HostConfigDaemon:
data = self.config_db.get_table("FIPS")
self.fipscfg.fips_handler(data)

def serial_console_config_handler(self, key, op, data):
syslog.syslog(syslog.LOG_INFO, 'SERIAL_CONSOLE table handler...')
self.serialconscfg.update_serial_console_cfg(key, data)

def wait_till_system_init_done(self):
# No need to print the output in the log file so using the "--quiet"
# flag
Expand All @@ -1742,6 +1817,8 @@ class HostConfigDaemon:
self.config_db.subscribe('RADIUS_SERVER', make_callback(self.radius_server_handler))
self.config_db.subscribe('PASSW_HARDENING', make_callback(self.passwh_handler))
self.config_db.subscribe('SSH_SERVER', make_callback(self.ssh_handler))
# Handle SERIAL_CONSOLE
self.config_db.subscribe('SERIAL_CONSOLE', make_callback(self.serial_console_config_handler))
# Handle IPTables configuration
self.config_db.subscribe('LOOPBACK_INTERFACE', make_callback(self.lpbk_handler))
# Handle updates to src intf changes in radius
Expand Down
42 changes: 42 additions & 0 deletions tests/hostcfgd/hostcfgd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,48 @@ def test_loopback_update(self):
])


class TestSerialConsoleCfgd(TestCase):
"""
Test hostcfd daemon - SerialConsoleCfg
"""
def setUp(self):
MockConfigDb.CONFIG_DB['SERIAL_CONSOLE'] = {
'POLICIES': {'inactivity-timeout': '15', 'sysrq-capabilities': 'disabled'}
}

def tearDown(self):
MockConfigDb.CONFIG_DB = {}

def test_serial_console_update_cfg(self):
with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
popen_mock = mock.Mock()
attrs = {'communicate.return_value': ('output', 'error')}
popen_mock.configure_mock(**attrs)
mocked_subprocess.Popen.return_value = popen_mock

serialcfg = hostcfgd.SerialConsoleCfg()
serialcfg.update_serial_console_cfg(
'POLICIES', MockConfigDb.CONFIG_DB['SERIAL_CONSOLE']['POLICIES'])
mocked_subprocess.check_call.assert_has_calls([
call('sudo service serial-config restart'),
])

def test_serial_console_is_caching_config(self):
with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
popen_mock = mock.Mock()
attrs = {'communicate.return_value': ('output', 'error')}
popen_mock.configure_mock(**attrs)
mocked_subprocess.Popen.return_value = popen_mock

serialcfg = hostcfgd.SerialConsoleCfg()
serialcfg.cache['POLICIES'] = MockConfigDb.CONFIG_DB['SERIAL_CONSOLE']['POLICIES']

serialcfg.update_serial_console_cfg(
'POLICIES', MockConfigDb.CONFIG_DB['SERIAL_CONSOLE']['POLICIES'])

mocked_subprocess.check_call.assert_not_called()


class TestHostcfgdDaemon(TestCase):

def setUp(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/hostcfgd/sample_output/SSH_SERVER/sshd_config.old
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
# PermitTTY no
# ForceCommand cvs server
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveInterval 900
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
# PermitTTY no
# ForceCommand cvs server
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveInterval 900
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
# PermitTTY no
# ForceCommand cvs server
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveInterval 900
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
# PermitTTY no
# ForceCommand cvs server
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveInterval 900
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
# PermitTTY no
# ForceCommand cvs server
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveInterval 900
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
# PermitTTY no
# ForceCommand cvs server
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveInterval 900
40 changes: 40 additions & 0 deletions tests/hostcfgd/test_ssh_server_vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"authentication_retries": "6",
"login_timeout": "120",
"ports": "22",
"inactivity_timeout": "15",
"max_sessions": "0",
}
},
"DEVICE_METADATA": {
Expand All @@ -35,6 +37,12 @@
"num_dumps": "3",
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
}
},
"SERIAL_CONSOLE": {
"POLICIES":{
"inactivity_timeout": "15",
"sysrq_capabilities": "disabled"
}
}
},
"modify_authentication_retries":{
Expand All @@ -43,6 +51,8 @@
"authentication_retries": "12",
"login_timeout": "120",
"ports": "22",
"inactivity_timeout": "15",
"max_sessions": "0",
}
},
"DEVICE_METADATA": {
Expand All @@ -67,6 +77,12 @@
"num_dumps": "3",
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
}
},
"SERIAL_CONSOLE": {
"POLICIES":{
"inactivity_timeout": "15",
"sysrq_capabilities": "disabled"
}
}
},
"modify_login_timeout":{
Expand All @@ -75,6 +91,8 @@
"authentication_retries": "6",
"login_timeout": "60",
"ports": "22",
"inactivity_timeout": "15",
"max_sessions": "0",
}
},
"DEVICE_METADATA": {
Expand All @@ -99,6 +117,12 @@
"num_dumps": "3",
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
}
},
"SERIAL_CONSOLE": {
"POLICIES":{
"inactivity_timeout": "15",
"sysrq_capabilities": "disabled"
}
}
},
"modify_ports":{
Expand All @@ -107,6 +131,8 @@
"authentication_retries": "6",
"login_timeout": "120",
"ports": "22,23,24",
"inactivity_timeout": "15",
"max_sessions": "0",
}
},
"DEVICE_METADATA": {
Expand All @@ -132,13 +158,21 @@
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
}
},
"SERIAL_CONSOLE": {
"POLICIES":{
"inactivity_timeout": "15",
"sysrq_capabilities": "disabled"
}
}
},
"modify_all":{
"SSH_SERVER": {
"POLICIES":{
"authentication_retries": "16",
"login_timeout": "140",
"ports": "22,222",
"inactivity_timeout": "15",
"max_sessions": "0",
}
},
"DEVICE_METADATA": {
Expand All @@ -163,6 +197,12 @@
"num_dumps": "3",
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
}
},
"SERIAL_CONSOLE": {
"POLICIES":{
"inactivity_timeout": "15",
"sysrq_capabilities": "disabled"
}
}
}
}
Expand Down

0 comments on commit a239567

Please sign in to comment.