Skip to content

Commit dce6d35

Browse files
Liuqulguohan
Liuqu
authored andcommitted
[TACACS+]: Add configDB enforcer for TACACS+ (#1214)
* [TACACS+]: Add configDB enforcer for TACACS+ * hostcfgd - configDB enforcer for TACACS+, listen configDB to modify the pam configuration for Authentication in host * Add a service script for hostcfgd Signed-off-by: Chenchen Qi <[email protected]> * [TACACS+]: Generate conf file by template file * Generate common-auth-sonic and tacplus_nss.conf by jinja2 template Signed-off-by: Chenchen Qi <[email protected]>
1 parent e0af519 commit dce6d35

File tree

5 files changed

+257
-0
lines changed

5 files changed

+257
-0
lines changed

files/build_templates/sonic_debian_extension.j2

+6
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ sudo cp $IMAGE_CONFIGS/interfaces/*.j2 $FILESYSTEM_ROOT/usr/share/sonic/template
128128
# Copy initial interfaces configuration file, will be overwritten on first boot
129129
sudo cp $IMAGE_CONFIGS/interfaces/init_interfaces $FILESYSTEM_ROOT/etc/network
130130

131+
# Copy hostcfgd files
132+
sudo cp $IMAGE_CONFIGS/hostcfgd/hostcfgd.service $FILESYSTEM_ROOT/etc/systemd/system/
133+
sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable hostcfgd.service
134+
sudo cp $IMAGE_CONFIGS/hostcfgd/hostcfgd $FILESYSTEM_ROOT/usr/bin/
135+
sudo cp $IMAGE_CONFIGS/hostcfgd/*.j2 $FILESYSTEM_ROOT/usr/share/sonic/templates/
136+
131137
# Copy hostname configuration scripts
132138
sudo cp $IMAGE_CONFIGS/hostname/hostname-config.service $FILESYSTEM_ROOT/etc/systemd/system/
133139
sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable hostname-config.service
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# THIS IS AN AUTO-GENERATED FILE
2+
#
3+
# /etc/pam.d/common-auth- authentication settings common to all services
4+
# This file is included from other service-specific PAM config files,
5+
# and should contain a list of the authentication modules that define
6+
# the central authentication scheme for use on the system
7+
# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the
8+
# traditional Unix authentication mechanisms.
9+
#
10+
# here are the per-package modules (the "Primary" block)
11+
12+
{% if auth['login'] == 'local' %}
13+
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass
14+
15+
{% elif auth['login'] == 'local,tacacs+' %}
16+
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass
17+
{% for server in servers | sub(0, -1) %}
18+
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_tacplus.so server={{ server.ip }}:{{ server.tcp_port }} secret={{ server.passkey }} login={{ server.auth_type }} timeout={{ server.timeout }} try_first_pass
19+
{% endfor %}
20+
{% if servers | count %}
21+
{% set last_server = servers | last %}
22+
auth [success=1 default=ignore] pam_tacplus.so server={{ last_server.ip }}:{{ last_server.tcp_port }} secret={{ last_server.passkey }} login={{ last_server.auth_type }} timeout={{ last_server.timeout }} try_first_pass
23+
24+
{% endif %}
25+
{% elif auth['login'] == 'tacacs+' or auth['login'] == 'tacacs+,local' %}
26+
{% for server in servers %}
27+
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_tacplus.so server={{ server.ip }}:{{ server.tcp_port }} secret={{ server.passkey }} login={{ server.auth_type }} timeout={{ server.timeout }} try_first_pass
28+
{% endfor %}
29+
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass
30+
31+
{% else %}
32+
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass
33+
34+
{% endif %}
35+
#
36+
# here's the fallback if no module succeeds
37+
auth requisite pam_deny.so
38+
# prime the stack with a positive return value if there isn't one already;
39+
# this avoids us returning an error just because nothing sets a success code
40+
# since the modules above will each just jump around
41+
auth required pam_permit.so
42+
# and here are more per-package modules (the "Additional" block)
43+

files/image_config/hostcfgd/hostcfgd

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/python -u
2+
# -*- coding: utf-8 -*-
3+
4+
import os
5+
import sys
6+
import subprocess
7+
import syslog
8+
import jinja2
9+
from swsssdk import ConfigDBConnector
10+
11+
# FILE
12+
PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic"
13+
PAM_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/common-auth-sonic.j2"
14+
NSS_TACPLUS_CONF = "/etc/tacplus_nss.conf"
15+
NSS_TACPLUS_CONF_TEMPLATE = "/usr/share/sonic/templates/tacplus_nss.conf.j2"
16+
NSS_CONF = "/etc/nsswitch.conf"
17+
18+
# TACACS+
19+
TACPLUS_SERVER_PASSKEY_DEFAULT = ""
20+
TACPLUS_SERVER_TIMEOUT_DEFAULT = "5"
21+
TACPLUS_SERVER_AUTH_TYPE_DEFAULT = "pap"
22+
23+
24+
def is_true(val):
25+
if val == 'True' or val == 'true':
26+
return True
27+
else:
28+
return False
29+
30+
31+
def sub(l, start, end):
32+
return l[start:end]
33+
34+
35+
class AaaCfg(object):
36+
def __init__(self):
37+
self.auth_default = {
38+
'login': 'local',
39+
'failthrough': True,
40+
'fallback': True
41+
}
42+
self.tacplus_global_default = {
43+
'auth_type': TACPLUS_SERVER_AUTH_TYPE_DEFAULT,
44+
'timeout': TACPLUS_SERVER_TIMEOUT_DEFAULT,
45+
'passkey': TACPLUS_SERVER_PASSKEY_DEFAULT
46+
}
47+
self.auth = {}
48+
self.tacplus_global = {}
49+
self.tacplus_servers = {}
50+
self.debug = False
51+
52+
# Load conf from ConfigDb
53+
def load(self, aaa_conf, tac_global_conf, tacplus_conf):
54+
for row in aaa_conf:
55+
self.aaa_update(row, aaa_conf[row], modify_conf=False)
56+
for row in tac_global_conf:
57+
self.tacacs_global_update(row, tac_global_conf[row], modify_conf=False)
58+
for row in tacplus_conf:
59+
self.tacacs_server_update(row, tacplus_conf[row], modify_conf=False)
60+
self.modify_conf_file()
61+
62+
def aaa_update(self, key, data, modify_conf=True):
63+
if key == 'authentication':
64+
self.auth = data
65+
if 'failthrough' in data:
66+
self.auth['failthrough'] = is_true(data['failthrough'])
67+
if 'debug' in data:
68+
self.debug = is_true(data['debug'])
69+
if modify_conf:
70+
self.modify_conf_file()
71+
72+
def tacacs_global_update(self, key, data, modify_conf=True):
73+
if key == 'global':
74+
self.tacplus_global = data
75+
if modify_conf:
76+
self.modify_conf_file()
77+
78+
def tacacs_server_update(self, key, data, modify_conf=True):
79+
if data == {}:
80+
if key in self.tacplus_servers:
81+
del self.tacplus_servers[key]
82+
else:
83+
self.tacplus_servers[key] = data
84+
85+
if modify_conf:
86+
self.modify_conf_file()
87+
88+
def modify_conf_file(self):
89+
auth = self.auth_default.copy()
90+
auth.update(self.auth)
91+
tacplus_global = self.tacplus_global_default.copy()
92+
tacplus_global.update(self.tacplus_global)
93+
94+
servers_conf = []
95+
if self.tacplus_servers:
96+
for addr in self.tacplus_servers:
97+
server = tacplus_global.copy()
98+
server['ip'] = addr
99+
server.update(self.tacplus_servers[addr])
100+
servers_conf.append(server)
101+
sorted(servers_conf, key=lambda t: t['priority'], reverse=True)
102+
103+
template_file = os.path.abspath(PAM_AUTH_CONF_TEMPLATE)
104+
env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True)
105+
env.filters['sub'] = sub
106+
template = env.get_template(template_file)
107+
pam_conf = template.render(auth=auth, servers=servers_conf)
108+
with open(PAM_AUTH_CONF, 'w') as f:
109+
f.write(pam_conf)
110+
111+
# Modify common-auth include file in /etc/pam.d/login and sshd
112+
if os.path.isfile(PAM_AUTH_CONF):
113+
os.system("sed -i -e '/^@include/s/common-auth$/common-auth-sonic/' /etc/pam.d/sshd")
114+
os.system("sed -i -e '/^@include/s/common-auth$/common-auth-sonic/' /etc/pam.d/login")
115+
else:
116+
os.system("sed -i -e '/^@include/s/common-auth-sonic$/common-auth/' /etc/pam.d/sshd")
117+
os.system("sed -i -e '/^@include/s/common-auth-sonic$/common-auth/' /etc/pam.d/login")
118+
119+
# Add tacplus in nsswitch.conf if TACACS+ enable
120+
if 'tacacs+' in auth['login']:
121+
if os.path.isfile(NSS_CONF):
122+
os.system("sed -i -e '/tacplus/b' -e '/^passwd/s/compat/& tacplus/' /etc/nsswitch.conf")
123+
else:
124+
if os.path.isfile(NSS_CONF):
125+
os.system("sed -i -e '/^passwd/s/ tacplus//' /etc/nsswitch.conf")
126+
127+
# Set tacacs+ server in nss-tacplus conf
128+
template_file = os.path.abspath(NSS_TACPLUS_CONF_TEMPLATE)
129+
template = env.get_template(template_file)
130+
nss_tacplus_conf = template.render(debug=self.debug, servers=servers_conf)
131+
with open(NSS_TACPLUS_CONF, 'w') as f:
132+
f.write(nss_tacplus_conf)
133+
134+
135+
class HostConfigDaemon:
136+
def __init__(self):
137+
self.config_db = ConfigDBConnector()
138+
self.config_db.connect(wait_for_init=True, retry_on=True)
139+
syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success')
140+
aaa = self.config_db.get_table('AAA')
141+
tacacs_global = self.config_db.get_table('TACPLUS')
142+
tacacs_server = self.config_db.get_table('TACPLUS_SERVER')
143+
self.aaacfg = AaaCfg()
144+
self.aaacfg.load(aaa, tacacs_global, tacacs_server)
145+
146+
def aaa_handler(self, key, data):
147+
syslog.syslog(syslog.LOG_DEBUG, 'value for {} changed to {}'.format(key, data))
148+
self.aaacfg.aaa_update(key, data)
149+
150+
def tacacs_server_handler(self, key, data):
151+
syslog.syslog(syslog.LOG_DEBUG, 'value for {} changed to {}'.format(key, data))
152+
self.aaacfg.tacacs_server_update(key, data)
153+
154+
def tacacs_global_handler(self, key, data):
155+
syslog.syslog(syslog.LOG_DEBUG, 'value for {} changed to {}'.format(key, data))
156+
self.aaacfg.tacacs_global_update(key, data)
157+
158+
def start(self):
159+
self.config_db.subscribe('AAA', lambda table, key, data: self.aaa_handler(key, data))
160+
self.config_db.subscribe('TACPLUS_SERVER', lambda table, key, data: self.tacacs_server_handler(key, data))
161+
self.config_db.subscribe('TACPLUS', lambda table, key, data: self.tacacs_global_handler(key, data))
162+
self.config_db.listen()
163+
164+
165+
def main():
166+
daemon = HostConfigDaemon()
167+
daemon.start()
168+
169+
170+
if __name__ == "__main__":
171+
main()
172+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[Unit]
2+
Description=Host config enforcer daemon
3+
Requires=database.service
4+
After=database.service
5+
6+
[Service]
7+
Type=simple
8+
ExecStart=/usr/bin/hostcfgd
9+
10+
[Install]
11+
WantedBy=multi-user.target
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
onfiguration for libnss-tacplus
2+
3+
# debug - If you want to open debug log, set it on
4+
# Default: off
5+
# debug=on
6+
{% if debug %}
7+
debug=on
8+
{% endif %}
9+
10+
# server - set ip address, tcp port, secret string and timeout for TACACS+ servers
11+
# Default: None (no TACACS+ server)
12+
# server=1.1.1.1:49,secret=test,timeout=3
13+
{% for server in servers %}
14+
server={{ server.ip }}:{{ server.tcp_port }},secret={{ server.passkey }},timeout={{ server.timeout }}
15+
{% endfor %}
16+
17+
# user_priv - set the map between TACACS+ user privilege and local user's passwd
18+
# Default:
19+
# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/bin/bash
20+
# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/bin/bash
21+
22+
# many_to_one - create one local user for many TACACS+ users which has the same privilege
23+
# Default: many_to_one=n
24+
# many_to_one=y
25+

0 commit comments

Comments
 (0)