From 63a525711d1e41ffdda0f4d627b23c0612b3262b Mon Sep 17 00:00:00 2001 From: liuh-80 <58683130+liuh-80@users.noreply.github.com> Date: Tue, 9 Nov 2021 09:51:46 +0800 Subject: [PATCH] [TACACS+] Add config command for AAA authorization and accounting. (#1889) This pull request add config command for AAA authorization & accounting. #### Why I did it Support TACACS per-command authorization & accounting. #### How I did it Change AAA config command to support authorization & accounting. Change show AAA command to support authorization & accounting. Add UT to cover changed code. #### How to verify it 1. Build following project and pass all UTs: make target/python-wheels/sonic_utilities-1.2-py3-none-any.whl 2. Test new command manually. #### Which release branch to backport (provide reason below if selected) N/A #### Description for the changelog Add config command for AAA authorization & accounting. #### A picture of a cute animal (not mandatory but encouraged) --- config/aaa.py | 38 ++++++++++++ show/main.py | 10 ++++ tests/aaa_test.py | 144 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) diff --git a/config/aaa.py b/config/aaa.py index d39b00a20f09..18b467a804ff 100644 --- a/config/aaa.py +++ b/config/aaa.py @@ -137,6 +137,44 @@ def login(auth_protocol): add_table_kv('AAA', 'authentication', 'login', val) authentication.add_command(login) +# cmd: aaa authorization +@click.command() +@click.argument('protocol', nargs=-1, type=click.Choice([ "tacacs+", "local", "tacacs+ local"])) +def authorization(protocol): + """Switch AAA authorization [tacacs+ | local | '\"tacacs+ local\"']""" + if len(protocol) == 0: + click.echo('Argument "protocol" is required') + return + + if len(protocol) == 1 and (protocol[0] == 'tacacs+' or protocol[0] == 'local'): + add_table_kv('AAA', 'authorization', 'login', protocol[0]) + elif len(protocol) == 1 and protocol[0] == 'tacacs+ local': + add_table_kv('AAA', 'authorization', 'login', 'tacacs+,local') + else: + click.echo('Not a valid command') +aaa.add_command(authorization) + +# cmd: aaa accounting +@click.command() +@click.argument('protocol', nargs=-1, type=click.Choice(["disable", "tacacs+", "local", "tacacs+ local"])) +def accounting(protocol): + """Switch AAA accounting [disable | tacacs+ | local | '\"tacacs+ local\"']""" + if len(protocol) == 0: + click.echo('Argument "protocol" is required') + return + + if len(protocol) == 1: + if protocol[0] == 'tacacs+' or protocol[0] == 'local': + add_table_kv('AAA', 'accounting', 'login', protocol[0]) + elif protocol[0] == 'tacacs+ local': + add_table_kv('AAA', 'accounting', 'login', 'tacacs+,local') + elif protocol[0] == 'disable': + del_table_key('AAA', 'accounting', 'login') + else: + click.echo('Not a valid command') + else: + click.echo('Not a valid command') +aaa.add_command(accounting) @click.group() def tacacs(): diff --git a/show/main.py b/show/main.py index f4998218f2a2..1eed0623ae39 100755 --- a/show/main.py +++ b/show/main.py @@ -1478,10 +1478,20 @@ def aaa(db): 'authentication': { 'login': 'local (default)', 'failthrough': 'False (default)' + }, + 'authorization': { + 'login': 'local (default)' + }, + 'accounting': { + 'login': 'disable (default)' } } if 'authentication' in data: aaa['authentication'].update(data['authentication']) + if 'authorization' in data: + aaa['authorization'].update(data['authorization']) + if 'accounting' in data: + aaa['accounting'].update(data['accounting']) for row in aaa: entry = aaa[row] for key in entry: diff --git a/tests/aaa_test.py b/tests/aaa_test.py index d202b41ad7f7..57690e7af73b 100644 --- a/tests/aaa_test.py +++ b/tests/aaa_test.py @@ -18,18 +18,24 @@ show_aaa_default_output="""\ AAA authentication login local (default) AAA authentication failthrough False (default) +AAA authorization login local (default) +AAA accounting login disable (default) """ show_aaa_radius_output="""\ AAA authentication login radius AAA authentication failthrough False (default) +AAA authorization login local (default) +AAA accounting login disable (default) """ show_aaa_radius_local_output="""\ AAA authentication login radius,local AAA authentication failthrough False (default) +AAA authorization login local (default) +AAA accounting login disable (default) """ @@ -40,6 +46,54 @@ Not a valid command """ +show_aaa_tacacs_authentication_output="""\ +AAA authentication login tacacs+ +AAA authentication failthrough False (default) +AAA authorization login local (default) +AAA accounting login disable (default) + +""" + +show_aaa_tacacs_authorization_output="""\ +AAA authentication login tacacs+ +AAA authentication failthrough False (default) +AAA authorization login tacacs+ +AAA accounting login disable (default) + +""" + +show_aaa_tacacs_local_authorization_output="""\ +AAA authentication login tacacs+ +AAA authentication failthrough False (default) +AAA authorization login tacacs+,local +AAA accounting login disable (default) + +""" + +show_aaa_tacacs_accounting_output="""\ +AAA authentication login tacacs+ +AAA authentication failthrough False (default) +AAA authorization login tacacs+,local +AAA accounting login tacacs+ + +""" + +show_aaa_tacacs_local_accounting_output="""\ +AAA authentication login tacacs+ +AAA authentication failthrough False (default) +AAA authorization login tacacs+,local +AAA accounting login tacacs+,local + +""" + +show_aaa_disable_accounting_output="""\ +AAA authentication login tacacs+ +AAA authentication failthrough False (default) +AAA authorization login tacacs+,local +AAA accounting login disable + +""" + class TestAaa(object): @classmethod def setup_class(cls): @@ -136,3 +190,93 @@ def test_config_aaa_radius_invalid(self): assert result.exit_code == 0 assert result.output == config_aaa_not_a_valid_command_output + def test_config_aaa_tacacs(self, get_cmd_module): + (config, show) = get_cmd_module + runner = CliRunner() + db = Db() + db.cfgdb.delete_table("AAA") + + # test tacacs authentication + result = runner.invoke(config.config.commands["aaa"],\ + ["authentication", "login", "tacacs+"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_aaa_empty_output + + db.cfgdb.mod_entry("AAA", "authentication", {'login' : 'tacacs+'}) + + result = runner.invoke(show.cli.commands["aaa"], [], obj=db) + assert result.exit_code == 0 + assert result.output == show_aaa_tacacs_authentication_output + + # test tacacs authorization + result = runner.invoke(config.config.commands["aaa"],\ + ["authorization", "tacacs+"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_aaa_empty_output + + db.cfgdb.mod_entry("AAA", "authorization", {'login' : 'tacacs+'}) + + result = runner.invoke(show.cli.commands["aaa"], [], obj=db) + assert result.exit_code == 0 + assert result.output == show_aaa_tacacs_authorization_output + + # test tacacs + local authorization + result = runner.invoke(config.config.commands["aaa"],\ + ["authorization", "tacacs+ local"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_aaa_empty_output + + db.cfgdb.mod_entry("AAA", "authorization", {'login' : 'tacacs+,local'}) + + result = runner.invoke(show.cli.commands["aaa"], [], obj=db) + assert result.exit_code == 0 + assert result.output == show_aaa_tacacs_local_authorization_output + + # test tacacs accounting + result = runner.invoke(config.config.commands["aaa"],\ + ["accounting", "tacacs+"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_aaa_empty_output + + db.cfgdb.mod_entry("AAA", "accounting", {'login' : 'tacacs+'}) + + result = runner.invoke(show.cli.commands["aaa"], [], obj=db) + assert result.exit_code == 0 + assert result.output == show_aaa_tacacs_accounting_output + + # test tacacs + local accounting + result = runner.invoke(config.config.commands["aaa"],\ + ["accounting", "tacacs+ local"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_aaa_empty_output + + db.cfgdb.mod_entry("AAA", "accounting", {'login' : 'tacacs+,local'}) + + result = runner.invoke(show.cli.commands["aaa"], [], obj=db) + assert result.exit_code == 0 + assert result.output == show_aaa_tacacs_local_accounting_output + + # test disable accounting + result = runner.invoke(config.config.commands["aaa"],\ + ["accounting", "disable"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_aaa_empty_output + + db.cfgdb.mod_entry("AAA", "accounting", {'login' : 'disable'}) + + result = runner.invoke(show.cli.commands["aaa"], [], obj=db) + assert result.exit_code == 0 + assert result.output == show_aaa_disable_accounting_output +