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

[TACACS+]: Add configuration support for TACACS+ #125

Merged
merged 8 commits into from
Dec 14, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
208 changes: 208 additions & 0 deletions config/aaa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#!/usr/bin/env python -u
# -*- coding: utf-8 -*-

import click
import netaddr
from swsssdk import ConfigDBConnector


def is_ipaddress(val):
if not val:
return False
try:
netaddr.IPAddress(str(val))
except:
return False
return True


def add_table_kv(table, entry, key, val):
config_db = ConfigDBConnector()
config_db.connect()
config_db.mod_entry(table, entry, {key:val})


def del_table_key(table, entry, key):
config_db = ConfigDBConnector()
config_db.connect()
data = config_db.get_entry(table, entry)
if data:
if key in data:
del data[key]
config_db.set_entry(table, entry, data)


@click.group()
def aaa():
"""AAA command line"""
pass


# cmd: aaa authentication
@click.group()
def authentication():
"""User authentication"""
pass
aaa.add_command(authentication)


# cmd: aaa authentication failthrough
@click.command()
@click.argument('option', type=click.Choice(["enable", "disable", "default"]))
def failthrough(option):
"""Allow AAA fail-through [enable | disable | default]"""
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the default value here? enable/disable?

Copy link
Contributor

Choose a reason for hiding this comment

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

gulv@str-s6000-on-4:~$ show aaa
AAA authentication login tacacs+,local

when it is set to default, what is the actual value? enable or disable? same question for failthrough

Copy link
Author

Choose a reason for hiding this comment

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

The default value means there is no value for this configuration in configdb. For AAA authentication, the default is local authentication. For AAA fail-through, the default is enable.
I will add all the default value in the show command.

if option == 'default':
del_table_key('AAA', 'authentication', 'failthrough')
else:
if option == 'enable':
add_table_kv('AAA', 'authentication', 'failthrough', True)
elif option == 'disable':
add_table_kv('AAA', 'authentication', 'failthrough', False)
authentication.add_command(failthrough)


# cmd: aaa authentication fallback
@click.command()
@click.argument('option', type=click.Choice(["enable", "disable", "default"]))
def fallback(option):
"""Allow AAA fallback [enable | disable | default]"""
if option == 'default':
del_table_key('AAA', 'authentication', 'fallback')
else:
if option == 'enable':
add_table_kv('AAA', 'authentication', 'fallback', True)
elif option == 'disable':
add_table_kv('AAA', 'authentication', 'fallback', False)
authentication.add_command(fallback)


@click.command()
@click.argument('auth_protocol', nargs=-1, type=click.Choice(["tacacs+", "local", "default"]))
def login(auth_protocol):
"""Switch login authentication [ {tacacs+, local} | default ]"""
if len(auth_protocol) is 0:
print 'Not support empty argument'
return

if 'default' in auth_protocol:
del_table_key('AAA', 'authentication', 'login')
else:
val = auth_protocol[0]
if len(auth_protocol) == 2:
val += ',' + auth_protocol[1]
add_table_kv('AAA', 'authentication', 'login', val)
authentication.add_command(login)


@click.group()
def tacacs():
"""TACACS+ server configuration"""
pass


@click.group()
@click.pass_context
def default(ctx):
"""set its default configuration"""
ctx.obj = 'default'
tacacs.add_command(default)


@click.command()
@click.argument('second', metavar='<time_second>', type=click.IntRange(0, 60), required=False)
@click.pass_context
def timeout(ctx, second):
"""Specify TACACS+ server global timeout <0 - 60>"""
if ctx.obj == 'default':
del_table_key('TACPLUS', 'global', 'timeout')
elif second:
add_table_kv('TACPLUS', 'global', 'timeout', second)
else:
click.echo('Not support empty argument')
tacacs.add_command(timeout)
default.add_command(timeout)


@click.command()
@click.argument('type', metavar='<type>', type=click.Choice(["chap", "pap", "mschap"]), required=False)
@click.pass_context
def authtype(ctx, type):
"""Specify TACACS+ server global auth_type [chap | pap | mschap]"""
if ctx.obj == 'default':
del_table_key('TACPLUS', 'global', 'auth_type')
elif type:
add_table_kv('TACPLUS', 'global', 'auth_type', type)
else:
click.echo('Not support empty argument')
tacacs.add_command(authtype)
default.add_command(authtype)


@click.command()
@click.argument('secret', metavar='<secret_string>', required=False)
@click.pass_context
def passkey(ctx, secret):
"""Specify TACACS+ server global passkey <STRING>"""
if ctx.obj == 'default':
del_table_key('TACPLUS', 'global', 'passkey')
elif secret:
add_table_kv('TACPLUS', 'global', 'passkey', secret)
else:
click.echo('Not support empty argument')
tacacs.add_command(passkey)
default.add_command(passkey)


# cmd: tacacs add <ip_address> --timeout SECOND --key SECRET --type TYPE --port PORT --pri PRIORITY
@click.command()
@click.argument('address', metavar='<ip_address>')
@click.option('-t', '--timeout', help='Transmission timeout interval, default 5', type=int)
@click.option('-k', '--key', help='Shared secret')
@click.option('-a', '--auth_type', help='Authentication type, default pap', type=click.Choice(["chap", "pap", "mschap"]))
@click.option('-o', '--port', help='TCP port range is 1 to 65535, default 49', type=click.IntRange(1, 65535), default=49)
@click.option('-p', '--pri', help="Priority, default 1", type=click.IntRange(1, 64), default=1)
def add(address, timeout, key, auth_type, port, pri):
"""Specify a TACACS+ server"""
if not is_ipaddress(address):
click.echo('Invalid ip address')
return

config_db = ConfigDBConnector()
config_db.connect()
old_data = config_db.get_entry('TACPLUS_SERVER', address)
if old_data != {}:
click.echo('server %s already exists' % address)
else:
data = {
'tcp_port': str(port),
'priority': pri
}
if auth_type is not None:
data['auth_type'] = auth_type
if timeout is not None:
data['timeout'] = str(timeout)
if key is not None:
data['passkey'] = key
config_db.set_entry('TACPLUS_SERVER', address, data)
tacacs.add_command(add)


# cmd: tacacs delete <ip_address>
# 'del' is keyword, replace with 'delete'
@click.command()
@click.argument('address', metavar='<ip_address>')
def delete(address):
"""Delete a TACACS+ server"""
if not is_ipaddress(address):
click.echo('Invalid ip address')
return

config_db = ConfigDBConnector()
config_db.connect()
config_db.set_entry('TACPLUS_SERVER', address, None)
tacacs.add_command(delete)


if __name__ == "__main__":
aaa()

4 changes: 4 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from swsssdk import ConfigDBConnector
from minigraph import parse_device_desc_xml

import aaa

SONIC_CFGGEN_PATH = "sonic-cfggen"
MINIGRAPH_PATH = "/etc/sonic/minigraph.xml"
MINIGRAPH_BGP_SESSIONS = "minigraph_bgp"
Expand Down Expand Up @@ -112,6 +114,8 @@ def cli():
"""SONiC command line - 'config' command"""
if os.geteuid() != 0:
exit("Root privileges are required for this operation")
cli.add_command(aaa.aaa)
cli.add_command(aaa.tacacs)

@cli.command()
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_test_suite():
'scripts/lldpshow',
'scripts/port2alias',
'scripts/portstat',
'scripts/teamshow',
'scripts/teamshow'
],
data_files=[
('/etc/bash_completion.d', glob.glob('data/etc/bash_completion.d/*')),
Expand Down
52 changes: 52 additions & 0 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,58 @@ def services():
else:
break

@cli.command()
def aaa():
"""Show AAA configuration in ConfigDb"""
config_db = ConfigDBConnector()
config_db.connect()
data = config_db.get_table('AAA')
output = ''

aaa = {
'authentication': {
'login': 'local (default)',
'failthrough': 'True (default)',
'fallback': 'True (default)'
}
}
aaa['authentication'].update(data['authentication'])
for row in aaa:
entry = aaa[row]
for key in entry:
output += ('AAA %s %s %s\n' % (row, key, str(entry[key])))
click.echo(output)


@cli.command()
def tacacs():
"""Show TACACS+ configuration"""
config_db = ConfigDBConnector()
config_db.connect()
output = ''
data = config_db.get_table('TACPLUS')

tacplus = {
'global': {
'auth_type': 'pap (default)',
'timeout': '5 (default)',
'passkey': '<EMPTY_STRING> (default)'
}
}
tacplus['global'].update(data['global'])
for key in tacplus['global']:
output += ('TACPLUS global %s %s\n' % (str(key), str(tacplus['global'][key])))

data = config_db.get_table('TACPLUS_SERVER')
if data != {}:
for row in data:
entry = data[row]
output += ('\nTACPLUS_SERVER address %s\n' % row)
for key in entry:
output += (' %s %s\n' % (key, str(entry[key])))
click.echo(output)


#
# 'session' command ###
#
Expand Down