Skip to content

Commit 0fdd9f9

Browse files
Liuqulguohan
authored andcommitted
[TACACS+]: Add configuration support for TACACS+ (#125)
* [TACACS+]: Add configuration support for TACACS+ * Add config and show commands for TACACS+ * Add hostcfgd to listen configDB for TACACS+ and AAA, modify the pam configuration for Authentication in host. Signed-off-by: chenchen.qcc@alibaba-inc.com * [TACACS+]: Update config command * Add help comments for TACACS+ command * Use 'default' command to recover TACACS+ configuration Signed-off-by: chenchen.qcc@alibaba-inc.com * [TACACS+]: Adapt to the change for set_entry in ConfigDBConnector * The method set_entry in class ConfigDBConnector has changed to update all column key-value tuples. Modify the config command to adapt to this API change. Signed-off-by: Chenchen Qi <chenchen.qcc@alibaba-inc.com> * [TACACS+]: Move hostcfgd to sonic-buildimage * Command list config aaa authentication login [{tacacs+, local} | default] config aaa authentication failthrough [enable | disable | default] config tacacs passkey <TEXT> config tacacs authtype [pap | chap | mschap] config tacacs timeout <0-60> config tacacs add <ip_address> --port <1–65535> --timeout <1–60> --key <TEXT> --type [pap | chap | mschap] --pri <1-64> config tacacs delete <ip_address> show aaa show tacacs Signed-off-by: Chenchen Qi <chenchen.qcc@alibaba-inc.com> * [TACACS+]: Replace set_entry with mod_entry * Replace set_entry with mod_entry when modify the specific key-value pair in configdb. Signed-off-by: Chenchen Qi <chenchen.qcc@alibaba-inc.com> * [TACACS+]: Add default value print for TACACS+ show command Signed-off-by: Chenchen Qi <chenchen.qcc@alibaba-inc.com>
1 parent be91f16 commit 0fdd9f9

4 files changed

Lines changed: 265 additions & 1 deletion

File tree

config/aaa.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#!/usr/bin/env python -u
2+
# -*- coding: utf-8 -*-
3+
4+
import click
5+
import netaddr
6+
from swsssdk import ConfigDBConnector
7+
8+
9+
def is_ipaddress(val):
10+
if not val:
11+
return False
12+
try:
13+
netaddr.IPAddress(str(val))
14+
except:
15+
return False
16+
return True
17+
18+
19+
def add_table_kv(table, entry, key, val):
20+
config_db = ConfigDBConnector()
21+
config_db.connect()
22+
config_db.mod_entry(table, entry, {key:val})
23+
24+
25+
def del_table_key(table, entry, key):
26+
config_db = ConfigDBConnector()
27+
config_db.connect()
28+
data = config_db.get_entry(table, entry)
29+
if data:
30+
if key in data:
31+
del data[key]
32+
config_db.set_entry(table, entry, data)
33+
34+
35+
@click.group()
36+
def aaa():
37+
"""AAA command line"""
38+
pass
39+
40+
41+
# cmd: aaa authentication
42+
@click.group()
43+
def authentication():
44+
"""User authentication"""
45+
pass
46+
aaa.add_command(authentication)
47+
48+
49+
# cmd: aaa authentication failthrough
50+
@click.command()
51+
@click.argument('option', type=click.Choice(["enable", "disable", "default"]))
52+
def failthrough(option):
53+
"""Allow AAA fail-through [enable | disable | default]"""
54+
if option == 'default':
55+
del_table_key('AAA', 'authentication', 'failthrough')
56+
else:
57+
if option == 'enable':
58+
add_table_kv('AAA', 'authentication', 'failthrough', True)
59+
elif option == 'disable':
60+
add_table_kv('AAA', 'authentication', 'failthrough', False)
61+
authentication.add_command(failthrough)
62+
63+
64+
# cmd: aaa authentication fallback
65+
@click.command()
66+
@click.argument('option', type=click.Choice(["enable", "disable", "default"]))
67+
def fallback(option):
68+
"""Allow AAA fallback [enable | disable | default]"""
69+
if option == 'default':
70+
del_table_key('AAA', 'authentication', 'fallback')
71+
else:
72+
if option == 'enable':
73+
add_table_kv('AAA', 'authentication', 'fallback', True)
74+
elif option == 'disable':
75+
add_table_kv('AAA', 'authentication', 'fallback', False)
76+
authentication.add_command(fallback)
77+
78+
79+
@click.command()
80+
@click.argument('auth_protocol', nargs=-1, type=click.Choice(["tacacs+", "local", "default"]))
81+
def login(auth_protocol):
82+
"""Switch login authentication [ {tacacs+, local} | default ]"""
83+
if len(auth_protocol) is 0:
84+
print 'Not support empty argument'
85+
return
86+
87+
if 'default' in auth_protocol:
88+
del_table_key('AAA', 'authentication', 'login')
89+
else:
90+
val = auth_protocol[0]
91+
if len(auth_protocol) == 2:
92+
val += ',' + auth_protocol[1]
93+
add_table_kv('AAA', 'authentication', 'login', val)
94+
authentication.add_command(login)
95+
96+
97+
@click.group()
98+
def tacacs():
99+
"""TACACS+ server configuration"""
100+
pass
101+
102+
103+
@click.group()
104+
@click.pass_context
105+
def default(ctx):
106+
"""set its default configuration"""
107+
ctx.obj = 'default'
108+
tacacs.add_command(default)
109+
110+
111+
@click.command()
112+
@click.argument('second', metavar='<time_second>', type=click.IntRange(0, 60), required=False)
113+
@click.pass_context
114+
def timeout(ctx, second):
115+
"""Specify TACACS+ server global timeout <0 - 60>"""
116+
if ctx.obj == 'default':
117+
del_table_key('TACPLUS', 'global', 'timeout')
118+
elif second:
119+
add_table_kv('TACPLUS', 'global', 'timeout', second)
120+
else:
121+
click.echo('Not support empty argument')
122+
tacacs.add_command(timeout)
123+
default.add_command(timeout)
124+
125+
126+
@click.command()
127+
@click.argument('type', metavar='<type>', type=click.Choice(["chap", "pap", "mschap"]), required=False)
128+
@click.pass_context
129+
def authtype(ctx, type):
130+
"""Specify TACACS+ server global auth_type [chap | pap | mschap]"""
131+
if ctx.obj == 'default':
132+
del_table_key('TACPLUS', 'global', 'auth_type')
133+
elif type:
134+
add_table_kv('TACPLUS', 'global', 'auth_type', type)
135+
else:
136+
click.echo('Not support empty argument')
137+
tacacs.add_command(authtype)
138+
default.add_command(authtype)
139+
140+
141+
@click.command()
142+
@click.argument('secret', metavar='<secret_string>', required=False)
143+
@click.pass_context
144+
def passkey(ctx, secret):
145+
"""Specify TACACS+ server global passkey <STRING>"""
146+
if ctx.obj == 'default':
147+
del_table_key('TACPLUS', 'global', 'passkey')
148+
elif secret:
149+
add_table_kv('TACPLUS', 'global', 'passkey', secret)
150+
else:
151+
click.echo('Not support empty argument')
152+
tacacs.add_command(passkey)
153+
default.add_command(passkey)
154+
155+
156+
# cmd: tacacs add <ip_address> --timeout SECOND --key SECRET --type TYPE --port PORT --pri PRIORITY
157+
@click.command()
158+
@click.argument('address', metavar='<ip_address>')
159+
@click.option('-t', '--timeout', help='Transmission timeout interval, default 5', type=int)
160+
@click.option('-k', '--key', help='Shared secret')
161+
@click.option('-a', '--auth_type', help='Authentication type, default pap', type=click.Choice(["chap", "pap", "mschap"]))
162+
@click.option('-o', '--port', help='TCP port range is 1 to 65535, default 49', type=click.IntRange(1, 65535), default=49)
163+
@click.option('-p', '--pri', help="Priority, default 1", type=click.IntRange(1, 64), default=1)
164+
def add(address, timeout, key, auth_type, port, pri):
165+
"""Specify a TACACS+ server"""
166+
if not is_ipaddress(address):
167+
click.echo('Invalid ip address')
168+
return
169+
170+
config_db = ConfigDBConnector()
171+
config_db.connect()
172+
old_data = config_db.get_entry('TACPLUS_SERVER', address)
173+
if old_data != {}:
174+
click.echo('server %s already exists' % address)
175+
else:
176+
data = {
177+
'tcp_port': str(port),
178+
'priority': pri
179+
}
180+
if auth_type is not None:
181+
data['auth_type'] = auth_type
182+
if timeout is not None:
183+
data['timeout'] = str(timeout)
184+
if key is not None:
185+
data['passkey'] = key
186+
config_db.set_entry('TACPLUS_SERVER', address, data)
187+
tacacs.add_command(add)
188+
189+
190+
# cmd: tacacs delete <ip_address>
191+
# 'del' is keyword, replace with 'delete'
192+
@click.command()
193+
@click.argument('address', metavar='<ip_address>')
194+
def delete(address):
195+
"""Delete a TACACS+ server"""
196+
if not is_ipaddress(address):
197+
click.echo('Invalid ip address')
198+
return
199+
200+
config_db = ConfigDBConnector()
201+
config_db.connect()
202+
config_db.set_entry('TACPLUS_SERVER', address, None)
203+
tacacs.add_command(delete)
204+
205+
206+
if __name__ == "__main__":
207+
aaa()
208+

config/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from swsssdk import ConfigDBConnector
1010
from minigraph import parse_device_desc_xml
1111

12+
import aaa
13+
1214
SONIC_CFGGEN_PATH = "sonic-cfggen"
1315
MINIGRAPH_PATH = "/etc/sonic/minigraph.xml"
1416
MINIGRAPH_BGP_SESSIONS = "minigraph_bgp"
@@ -112,6 +114,8 @@ def cli():
112114
"""SONiC command line - 'config' command"""
113115
if os.geteuid() != 0:
114116
exit("Root privileges are required for this operation")
117+
cli.add_command(aaa.aaa)
118+
cli.add_command(aaa.tacacs)
115119

116120
@cli.command()
117121
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false,

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def get_test_suite():
5757
'scripts/lldpshow',
5858
'scripts/port2alias',
5959
'scripts/portstat',
60-
'scripts/teamshow',
60+
'scripts/teamshow'
6161
],
6262
data_files=[
6363
('/etc/bash_completion.d', glob.glob('data/etc/bash_completion.d/*')),

show/main.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,58 @@ def services():
778778
else:
779779
break
780780

781+
@cli.command()
782+
def aaa():
783+
"""Show AAA configuration in ConfigDb"""
784+
config_db = ConfigDBConnector()
785+
config_db.connect()
786+
data = config_db.get_table('AAA')
787+
output = ''
788+
789+
aaa = {
790+
'authentication': {
791+
'login': 'local (default)',
792+
'failthrough': 'True (default)',
793+
'fallback': 'True (default)'
794+
}
795+
}
796+
aaa['authentication'].update(data['authentication'])
797+
for row in aaa:
798+
entry = aaa[row]
799+
for key in entry:
800+
output += ('AAA %s %s %s\n' % (row, key, str(entry[key])))
801+
click.echo(output)
802+
803+
804+
@cli.command()
805+
def tacacs():
806+
"""Show TACACS+ configuration"""
807+
config_db = ConfigDBConnector()
808+
config_db.connect()
809+
output = ''
810+
data = config_db.get_table('TACPLUS')
811+
812+
tacplus = {
813+
'global': {
814+
'auth_type': 'pap (default)',
815+
'timeout': '5 (default)',
816+
'passkey': '<EMPTY_STRING> (default)'
817+
}
818+
}
819+
tacplus['global'].update(data['global'])
820+
for key in tacplus['global']:
821+
output += ('TACPLUS global %s %s\n' % (str(key), str(tacplus['global'][key])))
822+
823+
data = config_db.get_table('TACPLUS_SERVER')
824+
if data != {}:
825+
for row in data:
826+
entry = data[row]
827+
output += ('\nTACPLUS_SERVER address %s\n' % row)
828+
for key in entry:
829+
output += (' %s %s\n' % (key, str(entry[key])))
830+
click.echo(output)
831+
832+
781833
#
782834
# 'session' command ###
783835
#

0 commit comments

Comments
 (0)