Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cli-sessions] #99

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
92 changes: 85 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not ssh_server_policies:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will break logic :(

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,45 @@ 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 +1616,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 +1639,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 +1648,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 +1675,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 +1788,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 Down Expand Up @@ -1743,6 +1819,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
Loading