Skip to content

Commit a239567

Browse files
i-davydenkoIvan Davydenko
authored andcommitted
[cli-sessions]
update hostcfgd: add serial-console and extend ssh-server modules, update existing tests, introduce serial-console test
1 parent 722b796 commit a239567

File tree

10 files changed

+176
-13
lines changed

10 files changed

+176
-13
lines changed

data/templates/limits.conf.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,8 @@
6666
# ftp - chroot /ftp
6767
# @student - maxlogins 4
6868

69+
{% if max_sessions and max_sessions | int > 0 -%}
70+
* - maxsyslogins {{ max_sessions }}
71+
{% endif -%}
72+
6973
# End of file

scripts/hostcfgd

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,14 @@ LINUX_DEFAULT_PASS_MAX_DAYS = 99999
4040
LINUX_DEFAULT_PASS_WARN_AGE = 7
4141

4242
# Ssh min-max values
43-
SSH_MIN_VALUES={"authentication_retries": 3, "login_timeout": 1, "ports": 1}
44-
SSH_MAX_VALUES={"authentication_retries": 100, "login_timeout": 600, "ports": 65535}
45-
SSH_CONFIG_NAMES={"authentication_retries": "MaxAuthTries" , "login_timeout": "LoginGraceTime"}
43+
SSH_MIN_VALUES={"authentication_retries": 3, "login_timeout": 1, "ports": 1,
44+
"inactivity_timeout": 0, "max_sessions": 0}
45+
SSH_MAX_VALUES={"authentication_retries": 100, "login_timeout": 600,
46+
"ports": 65535, "inactivity_timeout": 35000,
47+
"max_sessions": 100}
48+
SSH_CONFIG_NAMES={"authentication_retries": "MaxAuthTries",
49+
"login_timeout": "LoginGraceTime", "ports": "Port",
50+
"inactivity_timeout": "ClientAliveInterval"}
4651

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

@@ -1128,16 +1139,31 @@ class PamLimitsCfg(object):
11281139
self.config_db = config_db
11291140
self.hwsku = ""
11301141
self.type = ""
1142+
self.max_sessions = None
11311143

11321144
# Load config from ConfigDb and render config file/
11331145
def update_config_file(self):
11341146
device_metadata = self.config_db.get_table('DEVICE_METADATA')
1135-
if "localhost" not in device_metadata:
1147+
ssh_server_policies = {}
1148+
try:
1149+
ssh_server_policies = self.config_db.get_table('SSH_SERVER')
1150+
except KeyError:
1151+
"""Dont throw except in case we don`t have SSH_SERVER config."""
1152+
pass
1153+
1154+
if "localhost" not in device_metadata and "POLICIES" not in ssh_server_policies:
11361155
return
11371156

11381157
self.read_localhost_config(device_metadata["localhost"])
1158+
self.read_max_sessions_config(ssh_server_policies.get("POLICIES", None))
11391159
self.render_conf_file()
11401160

1161+
# Read max_sessions config
1162+
def read_max_sessions_config(self, ssh_server_policies):
1163+
if ssh_server_policies is not None:
1164+
max_sess_cfg = ssh_server_policies.get('max_sessions', 0)
1165+
self.max_sessions = max_sess_cfg if max_sess_cfg != 0 else None
1166+
11411167
# Read localhost config
11421168
def read_localhost_config(self, localhost):
11431169
if "hwsku" in localhost:
@@ -1154,7 +1180,6 @@ class PamLimitsCfg(object):
11541180
def render_conf_file(self):
11551181
env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True)
11561182
env.filters['sub'] = sub
1157-
11581183
try:
11591184
template_file = os.path.abspath(PAM_LIMITS_CONF_TEMPLATE)
11601185
template = env.get_template(template_file)
@@ -1168,7 +1193,8 @@ class PamLimitsCfg(object):
11681193
template = env.get_template(template_file)
11691194
limits_conf = template.render(
11701195
hwsku=self.hwsku,
1171-
type=self.type)
1196+
type=self.type,
1197+
max_sessions=self.max_sessions)
11721198
with open(LIMITS_CONF, 'w') as f:
11731199
f.write(limits_conf)
11741200
except Exception as e:
@@ -1500,6 +1526,44 @@ class FipsCfg(object):
15001526
syslog.syslog(syslog.LOG_INFO, f'FipsCfg: update the FIPS enforce option {self.enforce}.')
15011527
loader.set_fips(image, self.enforce)
15021528

1529+
1530+
class SerialConsoleCfg:
1531+
1532+
def __init__(self):
1533+
self.cache = {}
1534+
1535+
def load(self, cli_sessions_conf):
1536+
self.cache = cli_sessions_conf or {}
1537+
syslog.syslog(syslog.LOG_INFO,
1538+
f'SerialConsoleCfg: Initial config: {self.cache}')
1539+
1540+
def update_serial_console_cfg(self, key, data):
1541+
'''
1542+
Apply config flow:
1543+
inactivity_timeout | set here AND in ssh_config flow | serial-config.service restarted.
1544+
max_sessions | set by PamLimitsCfg | serial-config.service DOESNT restarted.
1545+
sysrq_capabilities | set here | serial-config.service restarted.
1546+
'''
1547+
1548+
# max_sessions applied in limits.conf by PamLimitsCfg
1549+
if key in ['max_sessions']:
1550+
syslog.syslog(syslog.LOG_DEBUG,
1551+
f'SerialConsoleCfg: skip max_sessions in SerialConsoleCfg apply config handler')
1552+
return
1553+
1554+
if self.cache.get(key, {}) != data:
1555+
''' Config changed, need to restart the serial-config.service '''
1556+
syslog.syslog(syslog.LOG_INFO, f'Set serial-config parameter {key} value: {data}')
1557+
try:
1558+
run_cmd('sudo service serial-config restart', True, True)
1559+
except Exception:
1560+
syslog.syslog(syslog.LOG_ERR, f'Failed to update {key} serial-config.service config')
1561+
return
1562+
self.cache.update({key: data})
1563+
1564+
return
1565+
1566+
15031567
class HostConfigDaemon:
15041568
def __init__(self):
15051569
self.state_db_conn = DBConnector(STATE_DB, 0)
@@ -1551,6 +1615,9 @@ class HostConfigDaemon:
15511615
# Initialize FipsCfg
15521616
self.fipscfg = FipsCfg(self.state_db_conn)
15531617

1618+
# Initialize SerialConsoleCfg
1619+
self.serialconscfg = SerialConsoleCfg()
1620+
15541621
def load(self, init_data):
15551622
aaa = init_data['AAA']
15561623
tacacs_global = init_data['TACPLUS']
@@ -1571,6 +1638,7 @@ class HostConfigDaemon:
15711638
ntp_global = init_data.get(swsscommon.CFG_NTP_GLOBAL_TABLE_NAME)
15721639
ntp_servers = init_data.get(swsscommon.CFG_NTP_SERVER_TABLE_NAME)
15731640
ntp_keys = init_data.get(swsscommon.CFG_NTP_KEY_TABLE_NAME)
1641+
serial_console = init_data.get('SERIAL_CONSOLE', {})
15741642

15751643
self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server)
15761644
self.iptables.load(lpbk_table)
@@ -1579,11 +1647,12 @@ class HostConfigDaemon:
15791647
self.sshscfg.load(ssh_server)
15801648
self.devmetacfg.load(dev_meta)
15811649
self.mgmtifacecfg.load(mgmt_ifc, mgmt_vrf)
1582-
15831650
self.rsyslogcfg.load(syslog_cfg, syslog_srv)
15841651
self.dnscfg.load(dns)
15851652
self.fipscfg.load(fips_cfg)
15861653
self.ntpcfg.load(ntp_global, ntp_servers, ntp_keys)
1654+
self.serialconscfg.load(serial_console)
1655+
self.pamLimitsCfg.update_config_file()
15871656

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

16061675
def ssh_handler(self, key, op, data):
16071676
self.sshscfg.policies_update(key, data)
1677+
self.pamLimitsCfg.update_config_file()
1678+
16081679
syslog.syslog(syslog.LOG_INFO, 'SSH Update: key: {}, op: {}, data: {}'.format(key, op, data))
16091680

16101681
def tacacs_server_handler(self, key, op, data):
@@ -1716,6 +1787,10 @@ class HostConfigDaemon:
17161787
data = self.config_db.get_table("FIPS")
17171788
self.fipscfg.fips_handler(data)
17181789

1790+
def serial_console_config_handler(self, key, op, data):
1791+
syslog.syslog(syslog.LOG_INFO, 'SERIAL_CONSOLE table handler...')
1792+
self.serialconscfg.update_serial_console_cfg(key, data)
1793+
17191794
def wait_till_system_init_done(self):
17201795
# No need to print the output in the log file so using the "--quiet"
17211796
# flag
@@ -1742,6 +1817,8 @@ class HostConfigDaemon:
17421817
self.config_db.subscribe('RADIUS_SERVER', make_callback(self.radius_server_handler))
17431818
self.config_db.subscribe('PASSW_HARDENING', make_callback(self.passwh_handler))
17441819
self.config_db.subscribe('SSH_SERVER', make_callback(self.ssh_handler))
1820+
# Handle SERIAL_CONSOLE
1821+
self.config_db.subscribe('SERIAL_CONSOLE', make_callback(self.serial_console_config_handler))
17451822
# Handle IPTables configuration
17461823
self.config_db.subscribe('LOOPBACK_INTERFACE', make_callback(self.lpbk_handler))
17471824
# Handle updates to src intf changes in radius

tests/hostcfgd/hostcfgd_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,48 @@ def test_loopback_update(self):
121121
])
122122

123123

124+
class TestSerialConsoleCfgd(TestCase):
125+
"""
126+
Test hostcfd daemon - SerialConsoleCfg
127+
"""
128+
def setUp(self):
129+
MockConfigDb.CONFIG_DB['SERIAL_CONSOLE'] = {
130+
'POLICIES': {'inactivity-timeout': '15', 'sysrq-capabilities': 'disabled'}
131+
}
132+
133+
def tearDown(self):
134+
MockConfigDb.CONFIG_DB = {}
135+
136+
def test_serial_console_update_cfg(self):
137+
with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
138+
popen_mock = mock.Mock()
139+
attrs = {'communicate.return_value': ('output', 'error')}
140+
popen_mock.configure_mock(**attrs)
141+
mocked_subprocess.Popen.return_value = popen_mock
142+
143+
serialcfg = hostcfgd.SerialConsoleCfg()
144+
serialcfg.update_serial_console_cfg(
145+
'POLICIES', MockConfigDb.CONFIG_DB['SERIAL_CONSOLE']['POLICIES'])
146+
mocked_subprocess.check_call.assert_has_calls([
147+
call('sudo service serial-config restart'),
148+
])
149+
150+
def test_serial_console_is_caching_config(self):
151+
with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
152+
popen_mock = mock.Mock()
153+
attrs = {'communicate.return_value': ('output', 'error')}
154+
popen_mock.configure_mock(**attrs)
155+
mocked_subprocess.Popen.return_value = popen_mock
156+
157+
serialcfg = hostcfgd.SerialConsoleCfg()
158+
serialcfg.cache['POLICIES'] = MockConfigDb.CONFIG_DB['SERIAL_CONSOLE']['POLICIES']
159+
160+
serialcfg.update_serial_console_cfg(
161+
'POLICIES', MockConfigDb.CONFIG_DB['SERIAL_CONSOLE']['POLICIES'])
162+
163+
mocked_subprocess.check_call.assert_not_called()
164+
165+
124166
class TestHostcfgdDaemon(TestCase):
125167

126168
def setUp(self):

tests/hostcfgd/sample_output/SSH_SERVER/sshd_config.old

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
118118
# PermitTTY no
119119
# ForceCommand cvs server
120120
PermitRootLogin yes
121-
ClientAliveInterval 120
121+
ClientAliveInterval 900

tests/hostcfgd/sample_output/SSH_SERVER_default_values/sshd_config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
118118
# PermitTTY no
119119
# ForceCommand cvs server
120120
PermitRootLogin yes
121-
ClientAliveInterval 120
121+
ClientAliveInterval 900

tests/hostcfgd/sample_output/SSH_SERVER_modify_all/sshd_config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
119119
# PermitTTY no
120120
# ForceCommand cvs server
121121
PermitRootLogin yes
122-
ClientAliveInterval 120
122+
ClientAliveInterval 900

tests/hostcfgd/sample_output/SSH_SERVER_modify_authentication_retries/sshd_config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
118118
# PermitTTY no
119119
# ForceCommand cvs server
120120
PermitRootLogin yes
121-
ClientAliveInterval 120
121+
ClientAliveInterval 900

tests/hostcfgd/sample_output/SSH_SERVER_modify_login_timeout/sshd_config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
118118
# PermitTTY no
119119
# ForceCommand cvs server
120120
PermitRootLogin yes
121-
ClientAliveInterval 120
121+
ClientAliveInterval 900

tests/hostcfgd/sample_output/SSH_SERVER_modify_ports/sshd_config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,4 @@ Subsystem sftp /usr/lib/openssh/sftp-server
120120
# PermitTTY no
121121
# ForceCommand cvs server
122122
PermitRootLogin yes
123-
ClientAliveInterval 120
123+
ClientAliveInterval 900

tests/hostcfgd/test_ssh_server_vectors.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"authentication_retries": "6",
1212
"login_timeout": "120",
1313
"ports": "22",
14+
"inactivity_timeout": "15",
15+
"max_sessions": "0",
1416
}
1517
},
1618
"DEVICE_METADATA": {
@@ -35,6 +37,12 @@
3537
"num_dumps": "3",
3638
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
3739
}
40+
},
41+
"SERIAL_CONSOLE": {
42+
"POLICIES":{
43+
"inactivity_timeout": "15",
44+
"sysrq_capabilities": "disabled"
45+
}
3846
}
3947
},
4048
"modify_authentication_retries":{
@@ -43,6 +51,8 @@
4351
"authentication_retries": "12",
4452
"login_timeout": "120",
4553
"ports": "22",
54+
"inactivity_timeout": "15",
55+
"max_sessions": "0",
4656
}
4757
},
4858
"DEVICE_METADATA": {
@@ -67,6 +77,12 @@
6777
"num_dumps": "3",
6878
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
6979
}
80+
},
81+
"SERIAL_CONSOLE": {
82+
"POLICIES":{
83+
"inactivity_timeout": "15",
84+
"sysrq_capabilities": "disabled"
85+
}
7086
}
7187
},
7288
"modify_login_timeout":{
@@ -75,6 +91,8 @@
7591
"authentication_retries": "6",
7692
"login_timeout": "60",
7793
"ports": "22",
94+
"inactivity_timeout": "15",
95+
"max_sessions": "0",
7896
}
7997
},
8098
"DEVICE_METADATA": {
@@ -99,6 +117,12 @@
99117
"num_dumps": "3",
100118
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
101119
}
120+
},
121+
"SERIAL_CONSOLE": {
122+
"POLICIES":{
123+
"inactivity_timeout": "15",
124+
"sysrq_capabilities": "disabled"
125+
}
102126
}
103127
},
104128
"modify_ports":{
@@ -107,6 +131,8 @@
107131
"authentication_retries": "6",
108132
"login_timeout": "120",
109133
"ports": "22,23,24",
134+
"inactivity_timeout": "15",
135+
"max_sessions": "0",
110136
}
111137
},
112138
"DEVICE_METADATA": {
@@ -132,13 +158,21 @@
132158
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
133159
}
134160
},
161+
"SERIAL_CONSOLE": {
162+
"POLICIES":{
163+
"inactivity_timeout": "15",
164+
"sysrq_capabilities": "disabled"
165+
}
166+
}
135167
},
136168
"modify_all":{
137169
"SSH_SERVER": {
138170
"POLICIES":{
139171
"authentication_retries": "16",
140172
"login_timeout": "140",
141173
"ports": "22,222",
174+
"inactivity_timeout": "15",
175+
"max_sessions": "0",
142176
}
143177
},
144178
"DEVICE_METADATA": {
@@ -163,6 +197,12 @@
163197
"num_dumps": "3",
164198
"memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M"
165199
}
200+
},
201+
"SERIAL_CONSOLE": {
202+
"POLICIES":{
203+
"inactivity_timeout": "15",
204+
"sysrq_capabilities": "disabled"
205+
}
166206
}
167207
}
168208
}

0 commit comments

Comments
 (0)