66from jsonpatch import JsonPatchConflict
77from jsonpointer import JsonPointerException
88import utilities_common .cli as clicommon
9+ from sonic_py_common .security_cipher import master_key_mgr
10+ import getpass
911
1012ADHOC_VALIDATION = True
1113RADIUS_MAXSERVERS = 8
1214RADIUS_PASSKEY_MAX_LEN = 65
1315VALID_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
1535def is_secret (secret ):
1636 return bool (re .match ('^' + '[^ #,]*' + '$' , secret ))
@@ -122,7 +142,7 @@ def trace(db, option):
122142@clicommon .pass_db
123143def 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 )
256324tacacs .add_command (passkey )
257325default .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