Skip to content

Commit 23759bb

Browse files
Merge pull request #629 from brodjieski/dev_2.0
Another round of 2.0 updates
2 parents 60c66a2 + 2049f86 commit 23759bb

File tree

9 files changed

+86
-34
lines changed

9 files changed

+86
-34
lines changed

config/default/templates/documents/adoc/rule.adoc.jinja

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,13 @@ Deploy a configuration profile containing the following payload.
8686
|===
8787

8888
|ID
89-
|{{ rule.rule_id }}
90-
{{ rule.severity if rule.severity is not none and rule.tags not in check_tags }}
89+
|{{ rule.rule_id }}
90+
91+
{% if rule.severity is not none %}
92+
|{% trans %}Severity{% endtrans %}
93+
|{{ rule.severity }}
94+
{% endif %}
95+
9196

9297
|{% trans %}References{% endtrans %}
9398
|

config/default/templates/shell_scripts/compliance_script.sh.jinja

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ run_scan(){
322322

323323
{% for profile in baseline.profile %}
324324
{% for rule in profile.rules %}
325-
{% if "manual" not in rule.tags %}
325+
{% if "manual" not in rule.tags and "Excluded" not in rule.section %}
326326
{% include "check.jinja" %}
327327
{% endif %}
328328
{% endfor %}
@@ -363,7 +363,9 @@ run_fix(){
363363

364364
{% for profile in baseline.profile %}
365365
{% for rule in profile.rules %}
366-
{% include "fix.jinja" %}
366+
{% if "manual" not in rule.tags and "Excluded" not in rule.section %}
367+
{% include "fix.jinja" %}
368+
{% endif %}
367369
{% endfor %}
368370
{% endfor %}
369371

config/default/templates/shell_scripts/restore_script.sh.jinja

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ run_default_state(){
175175

176176
{% for profile in baseline.profile %}
177177
{% for rule in profile.rules %}
178-
{% include "restore.jinja" %}
178+
{% if "manual" not in rule.tags and "Excluded" not in rule.section %}
179+
{% include "restore.jinja" %}
180+
{% endif %}
179181
{% endfor %}
180182
{% endfor %}
181183

legacy/includes/odv.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,23 @@
298298
"max": 90
299299
},
300300
"note": "Delay the availability of a software update in days, e.g. 30"
301+
},
302+
{
303+
"ruleId": "os_tv_content_allowed",
304+
"type": "number",
305+
"validation": {
306+
"min": 0,
307+
"max": 1000
308+
},
309+
"note": "Possible values, with the U.S. description of the rating level: `1000`: All | `600`: TV-MA | `500`: TV-14 | `400`: TV-PG | `300`: TV-G | `200`: TV-Y7 | `100`: TV-Y | `0`: None"
310+
},
311+
{
312+
"ruleId": "os_movie_content_allowed",
313+
"type": "number",
314+
"validation": {
315+
"min": 0,
316+
"max": 1000
317+
},
318+
"note": "Possible values, with the U.S. description of the rating level: `1000`: All | `500`: NC-17 | `400`: R | `300`: PG-13 | `200`: PG | `100`: G | `0`: None"
301319
}
302320
]
303-

src/mscp/classes/macsecurityrule.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
_SENTINEL = object()
3030

31+
3132
class Sectionmap(StrEnum):
3233
AUDIT = "auditing"
3334
AUTH = "authentication"
@@ -157,7 +158,6 @@ class References(BaseModelWithAccessors):
157158
bsi: bsiReferences | None = None
158159
custom_refs: customReferences | None = None
159160

160-
161161
def get_ref(
162162
self,
163163
key: str,
@@ -221,8 +221,9 @@ def _dump_fields(model) -> dict[str, Any]:
221221
# Not found
222222
if default is not _SENTINEL:
223223
return default
224-
raise KeyError(f"Field '{key}' not found in any namespace ({', '.join(search_order)})")
225-
224+
raise KeyError(
225+
f"Field '{key}' not found in any namespace ({', '.join(search_order)})"
226+
)
226227

227228

228229
class Macsecurityrule(BaseModelWithAccessors):
@@ -234,7 +235,6 @@ class Macsecurityrule(BaseModelWithAccessors):
234235
Attributes:
235236
title (str): The title of the security rule.
236237
rule_id (str): Unique identifier for the rule.
237-
severity (str): Severity level of the rule.
238238
discussion (str): Detailed discussion or rationale for the rule.
239239
references (References): Reference information (e.g., NIST, CIS) associated with the rule.
240240
odv (dict[str, Any] | None): Organizational Defined Values for the rule, if applicable.
@@ -254,7 +254,7 @@ class Macsecurityrule(BaseModelWithAccessors):
254254
os_version: float = Field(default_factory=float)
255255
check (str): The commands to evaluate the state of a rule.
256256
fix: (str): The commands to remediate and set the configuration for a rule.
257-
severity: (dict[str, Any]): The category for impact assigned to a rule for associated benchmarks.
257+
severity: (str): The category for impact assigned to a rule for associated benchmarks.
258258
default_state: (str): The command to restore the system to the default configuration for a rule.
259259
260260
Class Methods:
@@ -299,7 +299,7 @@ class Macsecurityrule(BaseModelWithAccessors):
299299
os_version: float = Field(default_factory=float)
300300
check: str | None = None
301301
fix: str | None = None
302-
severity: dict[str, Any] | None = None
302+
severity: str | None = None
303303
default_state: str | None = None
304304

305305
@classmethod
@@ -360,7 +360,7 @@ def load_rules(
360360
default_state_value: str | None = None
361361
mechanism: str = "Manual"
362362
payloads: list[Mobileconfigpayload] | None = []
363-
severity: dict[str, Any] | None = {}
363+
severity: str | None = None
364364
tags: list[str] = []
365365

366366
rule_file = next(
@@ -501,8 +501,8 @@ def load_rules(
501501
if benchmarks:
502502
for benchmark in benchmarks:
503503
name = benchmark.get("name")
504-
if "severity" in benchmark:
505-
severity[name] = benchmark["severity"]
504+
if "severity" in benchmark and name == parent_values:
505+
severity = benchmark.get("severity", "")
506506

507507
match tags:
508508
case "inherent":
@@ -536,6 +536,11 @@ def load_rules(
536536
cis: dict[str, Any] = rule_yaml["references"].get("cis", {})
537537
elif ref_key == "bsi":
538538
bsi: dict[str, Any] = rule_yaml["references"].get("bsi", {})
539+
elif ref_key == "custom": # support for 1.0 custom refs format
540+
for custom_ref_key in rule_yaml["references"]["custom"]:
541+
custom_refs[custom_ref_key] = rule_yaml["references"][
542+
"custom"
543+
].get(custom_ref_key, {})
539544
else:
540545
custom_refs[ref_key] = rule_yaml["references"].get(ref_key, {})
541546

@@ -588,16 +593,8 @@ def load_rules(
588593
bsi["indigo"] = [bsi["indigo"]]
589594
# Map custom references
590595
if custom_refs:
591-
if "custom_refs" in custom_refs and isinstance(
592-
custom_refs["custom_refs"], dict
593-
):
594-
if custom_refs["custom_refs"] is not None and not isinstance(
595-
custom_refs["custom_refs"], list
596-
):
597-
rule_yaml["references"]["custom_refs"] = {}
598-
rule_yaml["references"]["custom_refs"]["references"] = [
599-
custom_refs
600-
]
596+
rule_yaml["references"]["custom_refs"] = {}
597+
rule_yaml["references"]["custom_refs"]["references"] = [custom_refs]
601598

602599
rule = cls(
603600
**rule_yaml,

src/mscp/classes/payload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def _save_plist(
245245
settings_dict (dict[str, Any]): The settings to save.
246246
"""
247247
try:
248-
create_file(preferences_file, settings_dict)
248+
create_file(preferences_file, settings_dict, append=True)
249249
logger.success(f"Settings plist written to {preferences_file}")
250250
except Exception as e:
251251
logger.error(f"Error creating plist file {preferences_file}: {e}")

src/mscp/cli.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,6 @@ def parse_cli() -> None:
404404
)
405405
sys.exit()
406406

407-
if args.os_name == "visionos":
408-
logger.warning("visionOS is not supported at this time.")
409-
sys.exit()
410-
411407
if args.subcommand == "guidance":
412408
if args.os_name != "macos" and args.script:
413409
logger.error(

src/mscp/generate/baseline.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,31 @@ def collect_tags_and_benchmarks(
4646
return sorted(tags_set), benchmark_platforms
4747

4848

49+
def collect_established_benchmarks(
50+
rules: list[Macsecurityrule],
51+
) -> list[str]:
52+
"""
53+
Attempts to collect all established benchmarks in the MSCP library. An established
54+
benchmark is one where an ODV has been defined for a given benchmark.
55+
56+
Args:
57+
rules (list[Macsecurityrule]): A list of collected rules from the library.
58+
59+
Returns:
60+
list: A sorted set of discovered benchmarks
61+
"""
62+
established_benchmarks_set: set[str] = set()
63+
64+
for rule in rules:
65+
for odv in rule.odv or []:
66+
established_benchmarks_set.add(odv)
67+
68+
# remove "hint" from available benchmarks
69+
established_benchmarks_set.remove("hint")
70+
71+
return sorted(established_benchmarks_set)
72+
73+
4974
def print_keyword_summary(
5075
tags: list[str], benchmark_platforms: dict[str, set[str]]
5176
) -> None:
@@ -97,7 +122,7 @@ def generate_baseline(args: argparse.Namespace) -> None:
97122
baselines_data: dict = open_file(
98123
Path(config.get("includes_dir", ""), "800-53_baselines.yaml")
99124
)
100-
established_benchmarks: tuple[str, ...] = ("stig", "cis_lvl1", "cis_lvl2")
125+
101126
# removing misc_tags, unsure we need it.
102127
# misc_tags: tuple[str, str, str, str] = (
103128
# "permanent",
@@ -148,6 +173,8 @@ def replace_vars(text: str) -> str:
148173

149174
all_tags, benchmark_map = collect_tags_and_benchmarks(all_rules)
150175

176+
established_benchmarks: tuple[str, ...] = collect_established_benchmarks(all_rules)
177+
151178
if args.list_tags:
152179
print_keyword_summary(all_tags, benchmark_map)
153180

src/mscp/generate/guidance_support/profiles.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ def merge_flat_settings(flat_settings: List[Dict[str, Any]]) -> Dict[str, Any]:
130130
signed_output_path: Path = Path(build_path, "mobileconfigs", "signed")
131131
plist_output_path: Path = Path(build_path, "mobileconfigs", "preferences")
132132
granular_output_path: Path = Path(build_path, "mobileconfigs", "granular")
133-
granular_signed_output_path: Path = Path(build_path, "mobileconfigs", "granular", "signed")
133+
granular_signed_output_path: Path = Path(
134+
build_path, "mobileconfigs", "granular", "signed"
135+
)
134136

135137
make_dir(unsigned_output_path)
136138
make_dir(plist_output_path)
@@ -144,7 +146,10 @@ def merge_flat_settings(flat_settings: List[Dict[str, Any]]) -> Dict[str, Any]:
144146
make_dir(signed_output_path)
145147

146148
valid_rules: list[Macsecurityrule] = [
147-
rule for profile in baseline.profile for rule in profile.rules
149+
rule
150+
for profile in baseline.profile
151+
for rule in profile.rules
152+
if "Excluded" not in rule.section
148153
]
149154

150155
grouped_payloads: dict = get_payload_content_by_type(valid_rules)
@@ -218,7 +223,8 @@ def merge_flat_settings(flat_settings: List[Dict[str, Any]]) -> Dict[str, Any]:
218223
if signing:
219224
sign_config_profile(
220225
granular_output_path / f"{setting}.mobileconfig",
221-
granular_signed_output_path / f"{setting}.mobileconfig",
226+
granular_signed_output_path
227+
/ f"{setting}.mobileconfig",
222228
hash_value,
223229
)
224230
else:

0 commit comments

Comments
 (0)