Skip to content

Commit 10fdc7f

Browse files
Merge pull request #937 from thepurplebuffalo/update-sh-run-aaa-username
adjust parser for IOSXE 'show running-config aaa username' to be less rigid
2 parents 4cf2a53 + 5359268 commit 10fdc7f

File tree

4 files changed

+183
-111
lines changed

4 files changed

+183
-111
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--------------------------------------------------------------------------------
2+
New
3+
--------------------------------------------------------------------------------
4+
* IOSXE
5+
* Modified ShowRunningConfigAAAUsernameSchema(MetaParser):
6+
* added: optional 'autocommand'
7+
* added: optional 'nopassword'
8+
9+
* IOSXE
10+
* Modified ShowRunningConfigAAAUsername(ShowRunningConfigAAAUsernameSchema)
11+
* Added support for 'autocommand'
12+
* Added support for 'nopassword'
13+
* Added support for multiline usernames
14+
* Added logging (warning) for unsupported options
15+
16+
--------------------------------------------------------------------------------
17+
Fix
18+
--------------------------------------------------------------------------------
19+
* IOSXE
20+
* Modified ShowRunningConfigAAAUsername(ShowRunningConfigAAAUsernameSchema)
21+
* Changed how the cli() function parses arguments and parameters.
22+

src/genie/libs/parser/iosxe/show_run.py

Lines changed: 112 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# Python
1515
import re
16+
import logging
1617

1718
# Metaparser
1819
from genie.metaparser import MetaParser
@@ -22,6 +23,8 @@
2223
# import parser utils
2324
from genie.libs.parser.utils.common import Common
2425

26+
log = logging.getLogger(__name__)
27+
2528
# =================================================
2629
# Schema for:
2730
# * 'show run policy-map {name}'
@@ -2750,7 +2753,9 @@ class ShowRunningConfigAAAUsernameSchema(MetaParser):
27502753
Optional('common_criteria_policy'): str,
27512754
Optional('view'): str,
27522755
Optional('type'): str,
2756+
Optional('autocommand'): str,
27532757
Optional('onetime'): bool,
2758+
Optional('nopassword'): bool,
27542759
Optional('secret'): {
27552760
Optional('type'): int,
27562761
Optional('secret'): str,
@@ -2896,139 +2901,135 @@ def cli(self, output=None):
28962901
else:
28972902
out = output
28982903

2899-
# username testuser password 0 lab
2900-
p1 = re.compile(r'^username +(?P<username>\S+) +password +(?P<type>\d) +(?P<password>.*)$')
2904+
# NOTE: All of the following regular expressions should be anchored to
2905+
# the begining of the line ('^'). As each is used the line will be
2906+
# shortened. Think of this as popping arguments (and their parameters)
2907+
# off of a stack (the front of the line).
2908+
#
2909+
# There are some arguments that cannot have any subsequent arguments.
2910+
# These are:
2911+
# 1) password
2912+
# 2) secret
2913+
# 3) autocommand
2914+
# These arguments shall also match the end of the line ('$').
2915+
#
2916+
# All arguments that are not matched to the end of the line shall match
2917+
# an optional trailing space (' ?').
2918+
2919+
# username testuser
2920+
username_cmd = re.compile(r'^username (?P<username>\S+) ?')
29012921

2902-
# username testuser common-criteria-policy Test-CC password 0 password
2903-
p2 = re.compile(
2904-
r'^username +(?P<username>\S+) +common-criteria-policy +(?P<common_criteria_policy>.*) '
2905-
r'+password +(?P<type>\d) +(?P<password>.*)$')
2922+
# common-criteria-policy Test-CC
2923+
common_criteria_policy = re.compile(r'^common-criteria-policy (?P<common_criteria_policy>\S+) ?')
29062924

2907-
# username testuser secret 9 $9$A2OfV.30kNlIhE$ZEJQIT6aUj.TfCzqGQr.h4AmjQd/bWikQaGRlaLv0nQ
2908-
p3 = re.compile(r'^username +(?P<username>\S+) +secret +(?P<type>\d) +(?P<secret>.*)$')
2925+
# secret 9 $9$A2OfV.30kNlIhE$ZEJQIT6aUj.TfCzqGQr.h4AmjQd/bWikQaGRlaLv0nQ
2926+
secret = re.compile(r'^secret (?P<type>\d) (?P<secret>.*)$')
29092927

2910-
# username testuser one-time secret 9 $9$AuJ8xgW8aBBuF.$HyAzLk.3ILFsKrEvd4YjaAHbtonVMLikXw2pnrlkYJY
2911-
p4 = re.compile(
2912-
r'^username +(?P<username>\S+) +one-time +(?P<Onetime>)\s*secret +(?P<type>\d+) +(?P<secret>.*)$')
2928+
# privilege 15
2929+
privilege = re.compile(r'^privilege (?P<privilege>\d+) ?')
29132930

2914-
# username testuser privilege 15 password 0 lab
2915-
p5 = re.compile(
2916-
r'^username +(?P<username>\S+) +privilege +(?P<privilege>\d+) +password +(?P<type>\d) +(?P<password>.*)$')
2931+
# one-time
2932+
onetime = re.compile(r'^one-time ?')
29172933

2918-
# username testuser common-criteria-policy Test-CC secret 9 $9$7K9qbCZMJa2Vuk$6bS3.Bv7AkBXhTHpTH9V9fhMnJCQe1a9O7xBWHtOKo.
2919-
p6 = re.compile(
2920-
r'^username +(?P<username>\S+) +common-criteria-policy +(?P<common_criteria_policy>.*) '
2921-
r'+secret +(?P<type>\d) +(?P<secret>.*)$')
2934+
# nopassword
2935+
nopassword = re.compile(r'^nopassword ?')
29222936

2923-
# username testuser one-time password 0 password
2924-
p7 = re.compile(
2925-
r'^username +(?P<username>\S+) +one-time +(?P<Onetime>)\s*password +(?P<type>\d) +(?P<password>.*)$')
2937+
# password 0 lab
2938+
password = re.compile(r'^password (?P<type>\d) (?P<password>.*)$')
29262939

2927-
# username developer privilege 15 secret 9 $9$oNguEA9um9vRx.$MsDk0DOy1rzBjKAcySWdNjoKcA7GetG9YNnKOs8S67A
2928-
p8 = re.compile(r'^username +(?P<username>\S+) +privilege +(?P<privilege>\d+) +secret +(?P<secret_type>\d+) +(?P<secret>\S+)$')
2940+
# autocommand show ip bgp summary
2941+
autocommand = re.compile(r'^autocommand (?P<autocommand>.*)$')
29292942

29302943
# Initial return dictionary
29312944
ret_dict = {}
29322945

29332946
for line in out.splitlines():
29342947
line = line.strip()
29352948

2936-
# username testuser password 0 lab
2937-
m = p1.match(line)
2938-
if m:
2939-
group = m.groupdict()
2940-
username = group['username']
2941-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
2942-
pass_dict = users_dict.setdefault('password', {})
2943-
pass_dict['type'] = int(group['type'])
2944-
pass_dict['password'] = group['password']
2949+
# username testuser
2950+
m = username_cmd.match(line)
2951+
if not m:
2952+
# CLAIM: This is not a line with a 'username' command.
29452953
continue
29462954

2947-
# username testuser common-criteria-policy Test-CC password 0 password
2948-
m = p2.match(line)
2949-
if m:
2950-
group = m.groupdict()
2951-
username = group['username']
2952-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
2953-
users_dict['common_criteria_policy'] = group['common_criteria_policy']
2954-
pass_dict = users_dict.setdefault('password', {})
2955-
pass_dict['type'] = int(group['type'])
2956-
pass_dict['password'] = group['password']
2957-
continue
2955+
# CLAIM: this is a username line
2956+
# GOAL: extract the specified username and switch to that
2957+
# sub-dictionary:
2958+
group = m.groupdict()
2959+
username = group['username']
2960+
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
29582961

2959-
# username testuser secret 9 $9$A2OfV.30kNlIhE$ZEJQIT6aUj.TfCzqGQr.h4AmjQd/bWikQaGRlaLv0nQ
2960-
m = p3.match(line)
2961-
if m:
2962-
group = m.groupdict()
2963-
username = group['username']
2964-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
2965-
secret_dict = users_dict.setdefault('secret', {})
2966-
secret_dict['type'] = int(group['type'])
2967-
secret_dict['secret'] = group['secret']
2968-
continue
2962+
# GOAL: remove the matched portion from the begining of the line
2963+
# so that we can match the subsequent argument (if any):
2964+
line = line[m.end():]
29692965

2970-
# username testuser one-time secret 9 $9$AuJ8xgW8aBBuF.$HyAzLk.3ILFsKrEvd4YjaAHbtonVMLikXw2pnrlkYJY
2971-
m = p4.match(line)
2972-
if m:
2973-
group = m.groupdict()
2974-
username = group['username']
2975-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
2976-
users_dict['onetime'] = True
2977-
secret_dict = users_dict.setdefault('secret', {})
2978-
secret_dict['type'] = int(group['type'])
2979-
secret_dict['secret'] = group['secret']
2980-
continue
2966+
while line:
2967+
# GOAL: parse through the line an argument at a time,
2968+
# shortening the line as we go.
29812969

2982-
# username testuser privilege 15 password 0 lab
2983-
m = p5.match(line)
2984-
if m:
2985-
group = m.groupdict()
2986-
username = group['username']
2987-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
2988-
users_dict['privilege'] = int(group['privilege'])
2989-
pass_dict = users_dict.setdefault('password', {})
2990-
pass_dict['type'] = int(group['type'])
2991-
pass_dict['password'] = group['password']
2992-
continue
2970+
# GOAL: match the 'common-criteria-policy' option and return its parameter
2971+
# Sample: "common-criteria-policy MyPolicy"
2972+
if m := common_criteria_policy.match(line):
2973+
group = m.groupdict()
2974+
users_dict['common_criteria_policy'] = group['common_criteria_policy']
2975+
line = line[m.end():]
2976+
continue
29932977

2994-
# username testuser common-criteria-policy Test-CC secret 9 $9$7K9qbCZMJa2Vuk$6bS3.Bv7AkBXhTHpTH9V9fhMnJCQe1a9O7xBWHtOKo.
2995-
m = p6.match(line)
2996-
if m:
2997-
group = m.groupdict()
2998-
username = group['username']
2999-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
3000-
users_dict['common_criteria_policy'] = group['common_criteria_policy']
3001-
secret_dict = users_dict.setdefault('secret', {})
3002-
secret_dict['type'] = int(group['type'])
3003-
secret_dict['secret'] = group['secret']
3004-
continue
2978+
# GOAL: match the 'privilege' option and return its parameter
2979+
# Sample: "privilege 15"
2980+
if m := privilege.match(line):
2981+
group = m.groupdict()
2982+
users_dict['privilege'] = int(group['privilege'])
2983+
line = line[m.end():]
2984+
continue
30052985

3006-
# username testuser one-time password 0 password
3007-
m = p7.match(line)
3008-
if m:
3009-
group = m.groupdict()
3010-
username = group['username']
3011-
users_dict = ret_dict.setdefault('username', {}).setdefault(username, {})
3012-
users_dict['onetime'] = True
3013-
pass_dict = users_dict.setdefault('password', {})
3014-
pass_dict['type'] = int(group['type'])
3015-
pass_dict['password'] = group['password']
3016-
continue
2986+
# GOAL: match the 'secret' option and return its parameters ('type' and 'secret')
2987+
# Sample: "secret 9 $9$oNguEA9um9vRx.$MsDk0DOy1rzBjKAcySWdNjoKcA7GetG9YNnKOs8S67A"
2988+
if m := secret.match(line):
2989+
group = m.groupdict()
2990+
pass_dict = users_dict.setdefault('secret', {})
2991+
pass_dict['type'] = int(group['type'])
2992+
pass_dict['secret'] = group['secret']
2993+
line = line[m.end():]
2994+
continue
30172995

3018-
# username developer privilege 15 secret 9 $9$oNguEA9um9vRx.$MsDk0DOy1rzBjKAcySWdNjoKcA7GetG9YNnKOs8S67A
3019-
m = p8.match(line)
3020-
if m:
3021-
group = m.groupdict()
3022-
user_dict = ret_dict.setdefault('username', {}).setdefault(group['username'], {})
3023-
user_dict.update({
3024-
'privilege': int(group['privilege']),
3025-
})
2996+
# GOAL: match the 'onetime' flag
2997+
# Sample: "onetime"
2998+
if m := onetime.match(line):
2999+
group = m.groupdict()
3000+
users_dict['onetime'] = True
3001+
line = line[m.end():]
3002+
continue
30263003

3027-
secret_dict = user_dict.setdefault('secret', {})
3028-
secret_dict.update({
3029-
'type': int(group['secret_type']),
3030-
'secret': group['secret']
3031-
})
3004+
# GOAL: match the 'nopassword' flag
3005+
# Sample: "nopassword"
3006+
if m := nopassword.match(line):
3007+
group = m.groupdict()
3008+
users_dict['nopassword'] = True
3009+
line = line[m.end():]
3010+
continue
3011+
3012+
# GOAL: match the 'autocommand' option and return all subsequent text
3013+
# Sample: "autocommand show ip bgp summary"
3014+
if m := autocommand.match(line):
3015+
group = m.groupdict()
3016+
users_dict['autocommand'] = group['autocommand']
3017+
line = line[m.end():]
3018+
continue
3019+
3020+
# GOAL: match the 'password' option and return its parameters ('type' and 'password')
3021+
# Sample: "password 0 lab"
3022+
if m := password.match(line):
3023+
group = m.groupdict()
3024+
pass_dict = users_dict.setdefault('password', {})
3025+
pass_dict['type'] = int(group['type'])
3026+
pass_dict['password'] = group['password']
3027+
line = line[m.end():]
3028+
continue
3029+
3030+
# CLAIM: There is an unhandled argument.
3031+
log.warning(f"Unhandled argument in parser 'show running-config aaa username': {line}")
3032+
break
30323033

30333034
return ret_dict
30343035

@@ -5009,4 +5010,4 @@ def cli(self, output=None):
50095010
radius_server_dict.update({'dtls_trustpoint_server': m.groupdict()['dtls_trustpoint_server']})
50105011
continue
50115012

5012-
return ret_dict
5013+
return ret_dict
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
expected_output = {
2+
"username": {
3+
"testuser07": {
4+
"nopassword": True,
5+
"privilege": 3
6+
},
7+
"testuser08": {
8+
"common_criteria_policy": "Test-CC",
9+
"privilege": 15,
10+
"secret": {
11+
"secret": "$9$oNguEA9um9vRx.$MsDk0DOy1rzBjKAcySWdNjoKcA7GetG9YNnKOs8S67A",
12+
"type": 9
13+
}
14+
},
15+
"testuser09": {
16+
"autocommand": "show ip bgp summary",
17+
"privilege": 15,
18+
"secret": {
19+
"secret": "$9$UuxZCcqGu2IgBU$teHrzSPJK5FgLH0YAnUezoA1JwaqGBcJI4Xb6c3S7tU",
20+
"type": 9
21+
}
22+
},
23+
"testuser10": {
24+
"common_criteria_policy": "Test-CC",
25+
"password": {
26+
"password": "lab",
27+
"type": 0
28+
},
29+
"privilege": 15
30+
},
31+
"testuser11": {
32+
"privilege": 15
33+
}
34+
}
35+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
9400-HA#show running-config aaa username
2+
!
3+
! You may also need to setup a common criteria policy for testing:
4+
! aaa new-model
5+
! aaa common-criteria policy Test-CC
6+
! min-length 1
7+
username testuser07 privilege 3 nopassword
8+
username testuser08 privilege 15 common-criteria-policy Test-CC secret 9 $9$oNguEA9um9vRx.$MsDk0DOy1rzBjKAcySWdNjoKcA7GetG9YNnKOs8S67A
9+
! Some usernames can span multiple lines:
10+
username testuser09 privilege 15 secret 9 $9$UuxZCcqGu2IgBU$teHrzSPJK5FgLH0YAnUezoA1JwaqGBcJI4Xb6c3S7tU
11+
username testuser09 autocommand show ip bgp summary
12+
username testuser10 privilege 15 common-criteria-policy Test-CC password 0 lab
13+
! username with privilege and no password can happen if SSH pubkey auth is used:
14+
username testuser11 privilege 15

0 commit comments

Comments
 (0)