Skip to content

Commit 48997c2

Browse files
authored
Add Password Hardening CLI support (sonic-net#2338)
- What I did Add Password Hardening CLI - How I did it created the CLI by using YANG model generator, the YANG model can be found in the password hardening HLD https://github.com/Azure/SONiC/blob/master/doc/passw_hardening/hld_password_hardening.md#TestPlan and also in sonic-buildimage will be merged in the path: src/sonic-yang-models/yang-models/sonic-passwh.yang - How to verify it Manually: you can use configurations command like"config passw-hardening policies " or "show passw-hardening policies" (more examples in the HLD.) Auto: 1.There are unitest of each policy including good & bad flow in this commit, that should pass. 2.There are tests in sonic-mgmt repo in the path: sonic-mgmt/tests/passw_hardening/ the test are end to end test and the are testing the config/show CLI commands as well.
1 parent 414e239 commit 48997c2

File tree

5 files changed

+782
-0
lines changed

5 files changed

+782
-0
lines changed

config/plugins/sonic-passwh_yang.py

+380
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
import copy
2+
import click
3+
import utilities_common.cli as clicommon
4+
import utilities_common.general as general
5+
from config import config_mgmt
6+
7+
8+
# Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension.
9+
sonic_cfggen = general.load_module_from_source('sonic_cfggen', '/usr/local/bin/sonic-cfggen')
10+
11+
12+
def exit_with_error(*args, **kwargs):
13+
""" Print a message with click.secho and abort CLI.
14+
15+
Args:
16+
args: Positional arguments to pass to click.secho
17+
kwargs: Keyword arguments to pass to click.secho
18+
"""
19+
20+
click.secho(*args, **kwargs)
21+
raise click.Abort()
22+
23+
24+
def validate_config_or_raise(cfg):
25+
""" Validate config db data using ConfigMgmt.
26+
27+
Args:
28+
cfg (Dict): Config DB data to validate.
29+
Raises:
30+
Exception: when cfg does not satisfy YANG schema.
31+
"""
32+
33+
try:
34+
cfg = sonic_cfggen.FormatConverter.to_serialized(copy.deepcopy(cfg))
35+
config_mgmt.ConfigMgmt().loadData(cfg)
36+
except Exception as err:
37+
raise Exception('Failed to validate configuration: {}'.format(err))
38+
39+
40+
def update_entry_validated(db, table, key, data, create_if_not_exists=False):
41+
""" Update entry in table and validate configuration.
42+
If attribute value in data is None, the attribute is deleted.
43+
44+
Args:
45+
db (swsscommon.ConfigDBConnector): Config DB connector obect.
46+
table (str): Table name to add new entry to.
47+
key (Union[str, Tuple]): Key name in the table.
48+
data (Dict): Entry data.
49+
create_if_not_exists (bool):
50+
In case entry does not exists already a new entry
51+
is not created if this flag is set to False and
52+
creates a new entry if flag is set to True.
53+
Raises:
54+
Exception: when cfg does not satisfy YANG schema.
55+
"""
56+
57+
cfg = db.get_config()
58+
cfg.setdefault(table, {})
59+
60+
if not data:
61+
raise Exception(f"No field/values to update {key}")
62+
63+
if create_if_not_exists:
64+
cfg[table].setdefault(key, {})
65+
66+
if key not in cfg[table]:
67+
raise Exception(f"{key} does not exist")
68+
69+
entry_changed = False
70+
for attr, value in data.items():
71+
if value == cfg[table][key].get(attr):
72+
continue
73+
entry_changed = True
74+
if value is None:
75+
cfg[table][key].pop(attr, None)
76+
else:
77+
cfg[table][key][attr] = value
78+
79+
if not entry_changed:
80+
return
81+
82+
validate_config_or_raise(cfg)
83+
db.set_entry(table, key, cfg[table][key])
84+
85+
86+
@click.group(name="passw-hardening",
87+
cls=clicommon.AliasedGroup)
88+
def PASSW_HARDENING():
89+
""" PASSWORD HARDENING part of config_db.json """
90+
91+
pass
92+
93+
94+
95+
96+
@PASSW_HARDENING.group(name="policies",
97+
cls=clicommon.AliasedGroup)
98+
@clicommon.pass_db
99+
def PASSW_HARDENING_POLICIES(db):
100+
""" """
101+
102+
pass
103+
104+
105+
106+
107+
@PASSW_HARDENING_POLICIES.command(name="state")
108+
109+
@click.argument(
110+
"state",
111+
nargs=1,
112+
required=True,
113+
)
114+
@clicommon.pass_db
115+
def PASSW_HARDENING_POLICIES_state(db, state):
116+
""" state of the feature """
117+
118+
table = "PASSW_HARDENING"
119+
key = "POLICIES"
120+
data = {
121+
"state": state,
122+
}
123+
try:
124+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
125+
except Exception as err:
126+
exit_with_error(f"Error: {err}", fg="red")
127+
128+
129+
130+
@PASSW_HARDENING_POLICIES.command(name="expiration")
131+
132+
@click.argument(
133+
"expiration",
134+
nargs=1,
135+
required=True,
136+
)
137+
@clicommon.pass_db
138+
def PASSW_HARDENING_POLICIES_expiration(db, expiration):
139+
""" expiration time (days unit) """
140+
141+
table = "PASSW_HARDENING"
142+
key = "POLICIES"
143+
data = {
144+
"expiration": expiration,
145+
}
146+
try:
147+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
148+
except Exception as err:
149+
exit_with_error(f"Error: {err}", fg="red")
150+
151+
152+
153+
@PASSW_HARDENING_POLICIES.command(name="expiration-warning")
154+
155+
@click.argument(
156+
"expiration-warning",
157+
nargs=1,
158+
required=True,
159+
)
160+
@clicommon.pass_db
161+
def PASSW_HARDENING_POLICIES_expiration_warning(db, expiration_warning):
162+
""" expiration warning time (days unit) """
163+
164+
table = "PASSW_HARDENING"
165+
key = "POLICIES"
166+
data = {
167+
"expiration_warning": expiration_warning,
168+
}
169+
try:
170+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
171+
except Exception as err:
172+
exit_with_error(f"Error: {err}", fg="red")
173+
174+
175+
176+
@PASSW_HARDENING_POLICIES.command(name="history-cnt")
177+
178+
@click.argument(
179+
"history-cnt",
180+
nargs=1,
181+
required=True,
182+
)
183+
@clicommon.pass_db
184+
def PASSW_HARDENING_POLICIES_history_cnt(db, history_cnt):
185+
""" num of old password that the system will recorded """
186+
187+
table = "PASSW_HARDENING"
188+
key = "POLICIES"
189+
data = {
190+
"history_cnt": history_cnt,
191+
}
192+
try:
193+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
194+
except Exception as err:
195+
exit_with_error(f"Error: {err}", fg="red")
196+
197+
198+
199+
@PASSW_HARDENING_POLICIES.command(name="len-min")
200+
201+
@click.argument(
202+
"len-min",
203+
nargs=1,
204+
required=True,
205+
)
206+
@clicommon.pass_db
207+
def PASSW_HARDENING_POLICIES_len_min(db, len_min):
208+
""" password min length """
209+
210+
table = "PASSW_HARDENING"
211+
key = "POLICIES"
212+
data = {
213+
"len_min": len_min,
214+
}
215+
try:
216+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
217+
except Exception as err:
218+
exit_with_error(f"Error: {err}", fg="red")
219+
220+
221+
222+
@PASSW_HARDENING_POLICIES.command(name="reject-user-passw-match")
223+
224+
@click.argument(
225+
"reject-user-passw-match",
226+
nargs=1,
227+
required=True,
228+
)
229+
@clicommon.pass_db
230+
def PASSW_HARDENING_POLICIES_reject_user_passw_match(db, reject_user_passw_match):
231+
""" username password match """
232+
233+
table = "PASSW_HARDENING"
234+
key = "POLICIES"
235+
data = {
236+
"reject_user_passw_match": reject_user_passw_match,
237+
}
238+
try:
239+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
240+
except Exception as err:
241+
exit_with_error(f"Error: {err}", fg="red")
242+
243+
244+
245+
@PASSW_HARDENING_POLICIES.command(name="lower-class")
246+
247+
@click.argument(
248+
"lower-class",
249+
nargs=1,
250+
required=True,
251+
)
252+
@clicommon.pass_db
253+
def PASSW_HARDENING_POLICIES_lower_class(db, lower_class):
254+
""" password lower chars policy """
255+
256+
table = "PASSW_HARDENING"
257+
key = "POLICIES"
258+
data = {
259+
"lower_class": lower_class,
260+
}
261+
try:
262+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
263+
except Exception as err:
264+
exit_with_error(f"Error: {err}", fg="red")
265+
266+
267+
268+
@PASSW_HARDENING_POLICIES.command(name="upper-class")
269+
270+
@click.argument(
271+
"upper-class",
272+
nargs=1,
273+
required=True,
274+
)
275+
@clicommon.pass_db
276+
def PASSW_HARDENING_POLICIES_upper_class(db, upper_class):
277+
""" password upper chars policy """
278+
279+
table = "PASSW_HARDENING"
280+
key = "POLICIES"
281+
data = {
282+
"upper_class": upper_class,
283+
}
284+
try:
285+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
286+
except Exception as err:
287+
exit_with_error(f"Error: {err}", fg="red")
288+
289+
290+
291+
@PASSW_HARDENING_POLICIES.command(name="digits-class")
292+
293+
@click.argument(
294+
"digits-class",
295+
nargs=1,
296+
required=True,
297+
)
298+
@clicommon.pass_db
299+
def PASSW_HARDENING_POLICIES_digits_class(db, digits_class):
300+
""" password digits chars policy """
301+
302+
table = "PASSW_HARDENING"
303+
key = "POLICIES"
304+
data = {
305+
"digits_class": digits_class,
306+
}
307+
try:
308+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
309+
except Exception as err:
310+
exit_with_error(f"Error: {err}", fg="red")
311+
312+
313+
314+
@PASSW_HARDENING_POLICIES.command(name="special-class")
315+
316+
@click.argument(
317+
"special-class",
318+
nargs=1,
319+
required=True,
320+
)
321+
@clicommon.pass_db
322+
def PASSW_HARDENING_POLICIES_special_class(db, special_class):
323+
""" password special chars policy """
324+
325+
table = "PASSW_HARDENING"
326+
key = "POLICIES"
327+
data = {
328+
"special_class": special_class,
329+
}
330+
try:
331+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
332+
except Exception as err:
333+
exit_with_error(f"Error: {err}", fg="red")
334+
335+
336+
337+
338+
339+
340+
341+
342+
343+
344+
345+
346+
347+
348+
349+
350+
351+
352+
353+
354+
355+
356+
357+
358+
359+
360+
361+
362+
363+
364+
365+
366+
367+
368+
def register(cli):
369+
""" Register new CLI nodes in root CLI.
370+
371+
Args:
372+
cli: Root CLI node.
373+
Raises:
374+
Exception: when root CLI already has a command
375+
we are trying to register.
376+
"""
377+
cli_node = PASSW_HARDENING
378+
if cli_node.name in cli.commands:
379+
raise Exception(f"{cli_node.name} already exists in CLI")
380+
cli.add_command(PASSW_HARDENING)

0 commit comments

Comments
 (0)