Skip to content

Commit f01c202

Browse files
committed
Integrated Security Cipher for TACPLUS passkey encryption/decryption
1 parent f15e2d0 commit f01c202

File tree

1 file changed

+124
-8
lines changed

1 file changed

+124
-8
lines changed

config/aaa.py

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,31 @@
66
from jsonpatch import JsonPatchConflict
77
from jsonpointer import JsonPointerException
88
import utilities_common.cli as clicommon
9+
from sonic_py_common.security_cipher import master_key_mgr
10+
import getpass
911

1012
ADHOC_VALIDATION = True
1113
RADIUS_MAXSERVERS = 8
1214
RADIUS_PASSKEY_MAX_LEN = 65
1315
VALID_CHARS_MSG = "Valid chars are ASCII printable except SPACE, '#', and ','"
16+
TACACS_PASSKEY_MAX_LEN = 65
17+
18+
def rotate_tacplus_key(table_info):
19+
#Extract table and nested_key names
20+
table = table_info.split('|')[0]
21+
nested_key = table_info.split('|')[1]
22+
23+
# Re-encrypt with updated password
24+
value = secure_cipher.encrypt_passkey("TACPLUS", secret)
25+
add_table_kv(table, nested_key, 'passkey', value)
26+
27+
# Security cipher Callback dir
28+
# Note: Required for Security Cipher - password rotation feature
29+
security_cipher_clbk_lookup = {
30+
#TACPLUS
31+
"rotate_tacplus_key": rotate_tacplus_key
32+
}
33+
secure_cipher = master_key_mgr(security_cipher_clbk_lookup)
1434

1535
def is_secret(secret):
1636
return bool(re.match('^' + '[^ #,]*' + '$', secret))
@@ -122,7 +142,7 @@ def trace(db, option):
122142
@clicommon.pass_db
123143
def login(db, auth_protocol):
124144
"""Switch login authentication [ {ldap, radius, tacacs+, local} | default ]"""
125-
if len(auth_protocol) is 0:
145+
if len(auth_protocol) == 0:
126146
click.echo('Argument "auth_protocol" is required')
127147
return
128148
elif len(auth_protocol) > 2:
@@ -243,16 +263,64 @@ def authtype(db, ctx, type):
243263

244264
@click.command()
245265
@click.argument('secret', metavar='<secret_string>', required=False)
266+
@click.option('-e', '--encrypt', help='Enable secret encryption', is_flag=True)
267+
@click.option('-r', '--rotate', help='Rotate encryption secret', is_flag=True)
246268
@click.pass_context
247269
@clicommon.pass_db
248-
def passkey(db, ctx, secret):
270+
def passkey(db, ctx, secret, encrypt, rotate):
249271
"""Specify TACACS+ server global passkey <STRING>"""
250272
if ctx.obj == 'default':
251273
del_table_key(db, 'TACPLUS', 'global', 'passkey')
252274
elif secret:
253-
add_table_kv(db, 'TACPLUS', 'global', 'passkey', secret)
275+
if len(secret) > TACACS_PASSKEY_MAX_LEN:
276+
click.echo('Maximum of %d chars can be configured' % TACACS_PASSKEY_MAX_LEN)
277+
return
278+
elif not is_secret(secret):
279+
click.echo(VALID_CHARS_MSG)
280+
return
281+
282+
if encrypt:
283+
try:
284+
# Set new passwd if not set already
285+
if secure_cipher.is_key_encrypt_enabled("TACPLUS", "global") is False:
286+
#Register feature with Security Cipher module for the 1st time
287+
secure_cipher.register("TACPLUS", rotate_tacplus_key)
288+
passwd = getpass.getpass()
289+
#Set new password for encryption
290+
secure_cipher.set_feature_password("TACPLUS", passwd)
291+
else:
292+
#Check if password rotation is enabled
293+
if rotate:
294+
passwd = getpass.getpass()
295+
#Rotate password for TACPLUS feature and re-encrypt the secret
296+
secure_cipher.rotate_feature_passwd("TACPLUS", "TACPLUS|global", secret, passwd)
297+
return
298+
b64_encoded = secure_cipher.encrypt_passkey("TACPLUS", secret)
299+
if b64_encoded is not None:
300+
# Update key_encrypt flag
301+
add_table_kv('TACPLUS', 'global', 'key_encrypt', True)
302+
add_table_kv('TACPLUS', 'global', 'passkey', b64_encoded)
303+
else:
304+
#Deregister feature with Security Cipher module
305+
secure_cipher.deregister("TACPLUS", rotate_tacplus_key)
306+
click.echo('Passkey encryption failed: %s' % errs)
307+
return
308+
except (EOFError, KeyboardInterrupt):
309+
#Deregister feature with Security Cipher module
310+
secure_cipher.deregister("TACPLUS", rotate_tacplus_key)
311+
add_table_kv('TACPLUS', 'global', 'key_encrypt', False)
312+
click.echo('Input cancelled')
313+
return
314+
except Exception as e:
315+
#Deregister feature with Security Cipher module
316+
secure_cipher.deregister("TACPLUS", rotate_tacplus_key)
317+
add_table_kv('TACPLUS', 'global', 'key_encrypt', False)
318+
click.echo('Unexpected error: %s' %e)
319+
return
254320
else:
255-
click.echo('Argument "secret" is required')
321+
# Update key_encrypt flag to false
322+
add_table_kv('TACPLUS', 'global', 'key_encrypt', False)
323+
add_table_kv('TACPLUS', 'global', 'passkey', secret)
256324
tacacs.add_command(passkey)
257325
default.add_command(passkey)
258326

@@ -261,13 +329,15 @@ def passkey(db, ctx, secret):
261329
@click.command()
262330
@click.argument('address', metavar='<ip_address>')
263331
@click.option('-t', '--timeout', help='Transmission timeout interval, default 5', type=int)
264-
@click.option('-k', '--key', help='Shared secret')
332+
@click.option('-k', '--key', help='Shared secret, stored in plaintext')
333+
@click.option('-K', '--encrypted_key', help='Shared secret, stored in encrypted format')
334+
@click.option('-r', '--rotate', help='Rotate encryption secret', is_flag=True)
265335
@click.option('-a', '--auth_type', help='Authentication type, default pap', type=click.Choice(["chap", "pap", "mschap", "login"]))
266336
@click.option('-o', '--port', help='TCP port range is 1 to 65535, default 49', type=click.IntRange(1, 65535), default=49)
267337
@click.option('-p', '--pri', help="Priority, default 1", type=click.IntRange(1, 64), default=1)
268338
@click.option('-m', '--use-mgmt-vrf', help="Management vrf, default is no vrf", is_flag=True)
269339
@clicommon.pass_db
270-
def add(db, address, timeout, key, auth_type, port, pri, use_mgmt_vrf):
340+
def add(address, timeout, key, encrypted_key, rotate, auth_type, port, pri, use_mgmt_vrf, encrypt):
271341
"""Specify a TACACS+ server"""
272342
if ADHOC_VALIDATION:
273343
if not clicommon.is_ipaddress(address):
@@ -288,8 +358,54 @@ def add(db, address, timeout, key, auth_type, port, pri, use_mgmt_vrf):
288358
data['auth_type'] = auth_type
289359
if timeout is not None:
290360
data['timeout'] = str(timeout)
291-
if key is not None:
292-
data['passkey'] = key
361+
362+
if key and secret_key:
363+
raise click.UsageError("You must provide either --key or --secret_key")
364+
365+
if encrypted_key is not None:
366+
try:
367+
# Set new passwd if not set already
368+
if secure_cipher.is_key_encrypt_enabled("TACPLUS_SERVER", address) is False:
369+
#Register feature with Security Cipher module for the 1st time
370+
secure_cipher.register("TACPLUS", rotate_tacplus_key)
371+
passwd = getpass.getpass()
372+
#Set new password for encryption
373+
secure_cipher.set_feature_password("TACPLUS", passwd)
374+
else:
375+
#Check if password rotation is enabled
376+
if rotate:
377+
passwd = getpass.getpass()
378+
#Rotate password for TACPLUS feature and re-encrypt the secret
379+
secure_cipher.rotate_feature_passwd("TACPLUS", ("TACPLUS_SERVER|" + address), secret, passwd)
380+
return
381+
b64_encoded = secure_cipher.encrypt_passkey("TACPLUS", secret)
382+
if b64_encoded is not None:
383+
# Update key_encrypt flag
384+
add_table_kv('TACPLUS_SERVER', address, 'key_encrypt', True)
385+
add_table_kv('TACPLUS_SERVER', address, 'passkey', b64_encoded)
386+
else:
387+
#Deregister feature with Security Cipher module
388+
secure_cipher.deregister("TACPLUS", rotate_tacplus_key)
389+
click.echo('Passkey encryption failed: %s' % errs)
390+
return
391+
except (EOFError, KeyboardInterrupt):
392+
#Deregister feature with Security Cipher module
393+
secure_cipher.deregister("TACPLUS", rotate_tacplus_key)
394+
add_table_kv('TACPLUS_SERVER', address, 'key_encrypt', False)
395+
click.echo('Input cancelled')
396+
return
397+
except Exception as e:
398+
#Deregister feature with Security Cipher module
399+
secure_cipher.deregister("TACPLUS", rotate_tacplus_key)
400+
add_table_kv('TACPLUS_SERVER', address, 'key_encrypt', False)
401+
click.echo('Unexpected error: %s' %e)
402+
return
403+
else:
404+
if key is not None:
405+
# Update key_encrypt flag to false
406+
add_table_kv('TACPLUS_SERVER', address, 'key_encrypt', False)
407+
data['passkey'] = key
408+
293409
if use_mgmt_vrf :
294410
data['vrf'] = "mgmt"
295411
try:

0 commit comments

Comments
 (0)