Merge branch 'dev_mont_omg' into monterey

This commit is contained in:
Allen Golbig
2022-06-02 10:58:47 -04:00
36 changed files with 199 additions and 173 deletions

View File

@@ -7,6 +7,7 @@ authors: |
|Dan Brodjieski|National Aeronautics and Space Administration
|Allen Golbig|Jamf
|===
parent_values: recommended
profile:
- section: "authentication"
rules:

View File

@@ -7,6 +7,7 @@ authors: |
|Dan Brodjieski|National Aeronautics and Space Administration
|Allen Golbig|Jamf
|===
parent_values: recommended
profile:
- section: "authentication"
rules:

View File

@@ -7,6 +7,7 @@ authors: |
|Dan Brodjieski|National Aeronautics and Space Administration
|Allen Golbig|Jamf
|===
parent_values: recommended
profile:
- section: "authentication"
rules:

View File

@@ -7,6 +7,7 @@ authors: |
|Dan Brodjieski|National Aeronautics and Space Administration
|Allen Golbig|Jamf
|===
parent_values: recommended
profile:
- section: "authentication"
rules:

View File

@@ -7,6 +7,7 @@ authors: |
|Allen Golbig|Jamf
|Bob Gendler|National Institute of Standards and Technology
|===
parent_values: stig
profile:
- section: "authentication"
rules:

View File

@@ -7,6 +7,7 @@ authors: |
|Dan Brodjieski|National Aeronautics and Space Administration
|Allen Golbig|Jamf
|===
parent_values: recommended
profile:
- section: "authentication"
rules:

View File

@@ -8,6 +8,7 @@ authors: |
|Ron Colvin|Center for Internet Security
|Allen Golbig|Jamf
|===
parent_values: cis_lvl1
profile:
- section: "auditing"
rules:

View File

@@ -8,6 +8,7 @@ authors: |
|Ron Colvin|Center for Internet Security
|Allen Golbig|Jamf
|===
parent_values: cis_lvl2
profile:
- section: "auditing"
rules:

View File

@@ -9,6 +9,7 @@ authors: |
|Dan Brodjieski|National Aeronautics and Space Administration
|Allen Golbig|Jamf
|===
parent_values: recommended
profile:
- section: "authentication"
rules:

View File

@@ -30,7 +30,7 @@ macOS:
- "12.0"
odv:
hint: "Percentage of free space."
default: 25
recommended: 25
stig: 25
tags:
- 800-53r5_high

View File

@@ -38,7 +38,7 @@ macOS:
- "12.0"
odv:
hint: "See man audit_control for possible values."
default: 7d
recommended: 7d
stig: 7d
cis_lvl1: 60d or 1G
cis_lvl2: 60d or 1G

View File

@@ -40,7 +40,7 @@ macOS:
- "12.0"
odv:
hint: "Number of days."
default: 365
recommended: 365
cis_lvl1: 365
cis_lvl2: 365
tags:

View File

@@ -10,10 +10,7 @@ check: |
result:
string: "false"
fix: |
[source,bash]
----
This is implemented by a Configuration Profile.
----
references:
cce:
- CCE-91108-1

View File

@@ -56,7 +56,7 @@ macOS:
- "12.0"
odv:
hint: "Organization's Policy Text"
default: |-
recommended: |-
You are accessing a U.S. Government information system, which includes: 1) this computer, 2) this computer network, 3) all Government-furnished computers connected to this network, and 4) all Government-furnished devices and storage media attached to this network or to a computer on this network. You understand and consent to the following: you may access this information system for authorized use only; unauthorized use of the system is prohibited and subject to criminal and civil penalties; you have no reasonable expectation of privacy regarding any communication or data transiting or stored on this information system at any time and for any lawful Government purpose, the Government may monitor, intercept, audit, and search and seize any communication or data transiting or stored on this information system; and any communications or data transiting or stored on this information system may be disclosed or used for any lawful Government purpose. This information system may contain Controlled Unclassified Information (CUI) that is subject to safeguarding or dissemination controls in accordance with law, regulation, or Government-wide policy. Accessing and using this system indicates your understanding of this warning.
stig: |-
You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions:

View File

@@ -36,7 +36,7 @@ macOS:
- "12.0"
odv:
hint: "Organization's Policy Text"
default: |-
recommended: |-
You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.

View File

@@ -46,7 +46,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 0
recommended: 0
tags:
- 800-53r5_moderate
- 800-53r5_high

View File

@@ -49,7 +49,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 900
recommended: 900
tags:
- 800-53r5_moderate
- 800-53r5_high

View File

@@ -32,7 +32,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 0
recommended: 0
stig: 0
tags:
- 800-53r5_moderate

View File

@@ -35,7 +35,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 900
recommended: 900
stig: 900
tags:
- 800-53r5_moderate

View File

@@ -32,7 +32,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 30
recommended: 30
stig: 30
tags:
- stig

View File

@@ -34,7 +34,7 @@ macOS:
- "12.0"
odv:
hint: "Number of minutes."
default: 0
recommended: 0
cis_lvl1: 0
cis_lvl2: 0
tags:

View File

@@ -59,7 +59,7 @@ macOS:
- "12.0"
odv:
hint: "Number of days."
default: 35
recommended: 35
tags:
- 800-171
- cnssi-1253

View File

@@ -37,7 +37,7 @@ macOS:
- "12.0"
odv:
hint: "Number of failed attempts."
default: 3
recommended: 3
stig: 3
cis_lvl1: 5
cis_lvl2: 5

View File

@@ -37,7 +37,7 @@ macOS:
- "12.0"
odv:
hint: "Number of minutes."
default: 15
recommended: 15
stig: 15
tags:
- 800-171

View File

@@ -44,7 +44,7 @@ macOS:
- "12.0"
odv:
hint: "Number of previous passwords."
default: 5
recommended: 5
stig: 5
cis_lvl1: 15
cis_lvl2: 15

View File

@@ -45,7 +45,7 @@ macOS:
- "12.0"
odv:
hint: "Number of days."
default: 60
recommended: 60
stig: 60
tags:
- 800-171

View File

@@ -45,7 +45,7 @@ macOS:
- "12.0"
odv:
hint: "Minimum password length."
default: 15
recommended: 15
stig: 15
cis_lvl1: 15
cis_lvl2: 15

View File

@@ -63,7 +63,7 @@ macOS:
- "12.0"
odv:
hint: "Number of hours."
default: 24
recommended: 24
tags:
- 800-171
- cnssi-1253

View File

@@ -1,5 +1,5 @@
id: supplemental_cis_manual
title: " CIS Manual Recommendations"
title: "CIS Manual Recommendations"
discussion: |
List of CIS recommendations that are manual check in the CIS macOS Benchmark.

View File

@@ -35,7 +35,7 @@ macOS:
- "12.0"
odv:
hint: "Organization's approved message."
default: Center for Internet Security Test Message
recommended: Center for Internet Security Test Message
cis_lvl1: Center for Internet Security Test Message
cis_lvl2: Center for Internet Security Test Message
tags:

View File

@@ -44,7 +44,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 5
recommended: 5
stig: 5
cis_lvl1: 5
cis_lvl2: 5

View File

@@ -45,7 +45,7 @@ macOS:
- "12.0"
odv:
hint: "Number of seconds."
default: 1200
recommended: 1200
stig: 900
cis_lvl1: 1200
cis_lvl2: 1200
@@ -61,6 +61,7 @@ tags:
- cis_lvl2
- cisv8
- stig
- test
severity: "medium"
mobileconfig: true
mobileconfig_info:

View File

@@ -40,7 +40,7 @@ macOS:
- "12.0"
odv:
hint: "Name of timeserver(s) separated by commas."
default: "time-a.nist.gov,time-b.nist.gov"
recommended: "time-a.nist.gov,time-b.nist.gov"
stig: "time-a.nist.gov,time-b.nist.gov"
cis_lvl1: "time.apple.com"
cis_lvl2: "time.apple.com"

View File

@@ -212,7 +212,7 @@ def available_tags(all_rules):
print(tag)
return
def output_baseline(rules, os, keyword):
def output_baseline(rules, os, keyword, benchmark="recommended"):
inherent_rules = []
permanent_rules = []
na_rules = []
@@ -240,6 +240,7 @@ def output_baseline(rules, os, keyword):
output_text = f'title: "macOS {os}: Security Configuration - {keyword}"\n'
output_text += f'description: |\n This guide describes the actions to take when securing a macOS {os} system against the {keyword} baseline.\n'
output_text += f'authors: |\n |===\n |Name|Organization\n |===\n'
output_text += f'parent_values: "{benchmark}"\n'
output_text += 'profile:\n'
# sort the rules
@@ -345,19 +346,16 @@ def sanitised_input(prompt, type_=None, range_=None, default_=None):
else:
return ui
def odv_query(rules, keyword):
def odv_query(rules, benchmark):
print("The inclusion of any given rule is a risk-based-decision (RBD). While each rule is mapped to an 800-53 control, deploying it in your organization should be part of the decision-making process. \nYou will be prompted to include each rule, and for those with specific organizational defined values (ODV), you will be prompted for those as well.\n")
_established_benchmarks = ['stig', 'cis_lvl1', 'cis_lvl2']
if any(bm in keyword for bm in _established_benchmarks):
if not benchmark == "recommended":
print(f"WARNING: You are attempting to tailor an already established benchmark. Excluding rules or modifying ODVs may not meet the compliance of the established benchmark.\n")
benchmark = keyword
else:
benchmark = "default"
included_rules = []
queried_rule_ids = []
include_all = False
for rule in rules:
get_odv = False
@@ -366,30 +364,38 @@ def odv_query(rules, keyword):
if any(tag in rule.rule_tags for tag in _always_include):
#print(f"Including rule {rule.rule_id} by default")
include = "Y"
elif include_all:
include = "Y"
get_odv = True
queried_rule_ids.append(rule.rule_id)
remove_odv_custom_rule(rule)
else:
if rule.rule_id not in queried_rule_ids:
include = sanitised_input(f"Would you like to include the rule for \"{rule.rule_id}\" in your benchmark? [Y/n]: ", str.lower, range_=('y', 'n'), default_="y")
include = sanitised_input(f"Would you like to include the rule for \"{rule.rule_id}\" in your benchmark? [Y/n/all]: ", str.lower, range_=('y', 'n', 'all'), default_="y")
queried_rule_ids.append(rule.rule_id)
get_odv = True
# remove custom ODVs if there, they will be re-written if needed
remove_odv_custom_rule(rule)
if include.upper() == "ALL":
include_all = True
include = "y"
if include.upper() == "Y":
included_rules.append(rule)
if rule.rule_odv == "missing":
continue
elif get_odv:
if benchmark == "default":
if benchmark == "recommended":
print(f'{rule.rule_odv["hint"]}')
if isinstance(rule.rule_odv["default"], int):
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv["default"]}): ', int, default_=rule.rule_odv["default"])
elif isinstance(rule.rule_odv["default"], bool):
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv["default"]}): ', bool, default_=rule.rule_odv["default"])
if isinstance(rule.rule_odv["recommended"], int):
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the recommended value ({rule.rule_odv["recommended"]}): ', int, default_=rule.rule_odv["recommended"])
elif isinstance(rule.rule_odv["recommended"], bool):
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the recommended value ({rule.rule_odv["recommended"]}): ', bool, default_=rule.rule_odv["recommended"])
else:
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv["default"]}): ', str, default_=rule.rule_odv["default"])
if odv and odv != rule.rule_odv["default"]:
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the recommended value ({rule.rule_odv["recommended"]}): ', str, default_=rule.rule_odv["recommended"])
if odv and odv != rule.rule_odv["recommended"]:
write_odv_custom_rule(rule, odv)
else:
print(f'{rule.rule_odv["hint"]}')
print(f'\nODV value: {rule.rule_odv["hint"]}')
if isinstance(rule.rule_odv[benchmark], int):
odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv[benchmark]}): ', int, default_=rule.rule_odv[benchmark])
elif isinstance(rule.rule_odv[benchmark], bool):
@@ -467,10 +473,17 @@ def main():
print("No rules found for the keyword provided, please verify from the following list:")
available_tags(all_rules)
elif args.tailor:
_established_benchmarks = ['stig', 'cis_lvl1', 'cis_lvl2']
if any(bm in args.keyword for bm in _established_benchmarks):
benchmark = args.keyword
else:
benchmark = "recommended"
# prompt for name of benchmark to be used for filename
tailored_filename = sanitised_input(f'Enter a name for your tailored benchmark or press Enter for the default value ({args.keyword}): ', str, default_=args.keyword)
# prompt for inclusion, add ODV
odv_baseline_rules = odv_query(found_rules, args.keyword)
baseline_output_file = open(f"{build_path}/{args.keyword}.yaml", 'w')
baseline_output_file.write(output_baseline(odv_baseline_rules, version_yaml["os"], args.keyword))
odv_baseline_rules = odv_query(found_rules, benchmark)
baseline_output_file = open(f"{build_path}/{tailored_filename}.yaml", 'w')
baseline_output_file.write(output_baseline(odv_baseline_rules, version_yaml["os"], args.keyword, benchmark))
else:
baseline_output_file = open(f"{build_path}/{args.keyword}.yaml", 'w')
baseline_output_file.write(output_baseline(found_rules, version_yaml["os"], args.keyword))

View File

@@ -1,13 +1,10 @@
#!/usr/bin/env python3
# filename: generate_guidance.py
# description: Process a given baseline, and output guidance files
import types
import sys
import os.path
import plistlib
from unittest import result
import xlwt
import io
import glob
import os
import yaml
@@ -20,7 +17,6 @@ from xlwt import Workbook
from string import Template
from itertools import groupby
from uuid import uuid4
from collections import namedtuple
class MacSecurityRule():
@@ -283,7 +279,7 @@ class PayloadDict:
"""
output_file_path = output_path.name
preferences_path = os.path.dirname(output_file_path)
settings_dict = {}
for i in self.data['PayloadContent']:
@@ -302,7 +298,7 @@ class PayloadDict:
for setting in value['Forced']:
for key, value in setting['mcx_preference_settings'].items():
settings_dict[key] = value
#preferences_output_path = open(preferences_output_file, 'wb')
plistlib.dump(settings_dict, fp)
print(f"Settings plist written to {preferences_output_file}")
@@ -321,10 +317,10 @@ class PayloadDict:
for key,value in i.items():
if not key.startswith("Payload"):
settings_dict[key] = value
plistlib.dump(settings_dict, output_path)
print(f"Settings plist written to {output_path.name}")
def makeNewUUID():
return str(uuid4())
@@ -368,7 +364,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign
except OSError:
print("Creation of the directory %s failed" %
unsigned_mobileconfig_output_path)
if signing:
signed_mobileconfig_output_path = os.path.join(
f'{build_path}', 'mobileconfigs', 'signed')
@@ -406,7 +402,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign
#for rule in glob.glob('../rules/*/{}.yaml'.format(profile_rule)) + glob.glob('../custom/rules/**/{}.yaml'.format(profile_rule),recursive=True):
rule_yaml = get_rule_yaml(rule, custom)
if rule_yaml['mobileconfig']:
for payload_type, info in rule_yaml['mobileconfig_info'].items():
valid = True
@@ -432,7 +428,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign
profile_errors.append(rule)
logging.debug(e)
valid = False
if valid:
if payload_type == "com.apple.systemuiserver":
for setting_key, setting_value in info['mount-controls'].items():
@@ -486,7 +482,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign
displayname=displayname,
description=description)
if payload == "com.apple.ManagedClient.preferences":
for item in settings:
@@ -515,13 +511,13 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign
newProfile.finalizeAndSave(config_file)
newProfile.finalizeAndSavePlist(settings_config_file)
config_file.close()
print(f"""
CAUTION: These configuration profiles are intended for evaluation in a TEST
environment. Certain configuration profiles (Smartcards), when applied could
leave a system in a state where a user can no longer login with a password.
environment. Certain configuration profiles (Smartcards), when applied could
leave a system in a state where a user can no longer login with a password.
Please use caution when applying configuration settings to a system.
NOTE: If an MDM is already being leveraged, many of these profile settings may
be available through the vendor.
""")
@@ -529,7 +525,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign
def default_audit_plist(baseline_name, build_path, baseline_yaml):
""""Generate the default audit plist file to define exemptions
"""
# Output folder
plist_output_path = os.path.join(
f'{build_path}', 'preferences')
@@ -552,7 +548,7 @@ def default_audit_plist(baseline_name, build_path, baseline_yaml):
if profile_rule.startswith("supplemental"):
continue
plist_dict[profile_rule] = { "exempt": False }
plistlib.dump(plist_dict, plist_file)
@@ -571,7 +567,7 @@ def generate_script(baseline_name, build_path, baseline_yaml, reference):
## This script will attempt to audit all of the settings based on the installed profile.
## This script is provided as-is and should be fully tested on a system that is not in a production environment.
## This script is provided as-is and should be fully tested on a system that is not in a production environment.
################### Variables ###################
@@ -635,9 +631,9 @@ ask() {{
fi
# Ask the question - use /dev/tty in case stdin is redirected from somewhere else
/usr/bin/printf "${{YELLOW}} $1 [$prompt] ${{STD}}"
printf "${{YELLOW}} $1 [$prompt] ${{STD}}"
read REPLY
# Default?
if [ -z "$REPLY" ]; then
REPLY=$default
@@ -655,7 +651,7 @@ ask() {{
# function to display menus
show_menus() {{
/usr/bin/clear
/bin/echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
/bin/echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
/bin/echo " M A I N - M E N U"
/bin/echo " macOS Security Compliance Tool"
/bin/echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
@@ -685,7 +681,7 @@ compliance_count(){{
non_compliant=0
results=$(/usr/libexec/PlistBuddy -c "Print" /Library/Preferences/org.{baseline_name}.audit.plist)
while IFS= read -r line; do
if [[ "$line" =~ "finding = false" ]]; then
compliant=$((compliant+1))
@@ -694,8 +690,8 @@ compliance_count(){{
non_compliant=$((non_compliant+1))
fi
done <<< "$results"
# Enable output of just the compliant or non-compliant numbers.
# Enable output of just the compliant or non-compliant numbers.
if [[ $1 = "compliant" ]]
then
/bin/echo $compliant
@@ -713,18 +709,18 @@ generate_report(){{
count=($(compliance_count))
compliant=${{count[1]}}
non_compliant=${{count[2]}}
total=$((non_compliant + compliant))
percentage=$(printf %.2f $(( compliant * 100. / total )) )
/bin/echo
/bin/echo "Number of tests passed: ${{GREEN}}$compliant${{STD}}"
/bin/echo "Number of test FAILED: ${{RED}}$non_compliant${{STD}}"
/bin/echo "You are ${{YELLOW}}$percentage%${{STD}} percent compliant!"
echo "Number of tests passed: ${{GREEN}}$compliant${{STD}}"
echo "Number of test FAILED: ${{RED}}$non_compliant${{STD}}"
echo "You are ${{YELLOW}}$percentage%${{STD}} percent compliant!"
pause
}}
view_report(){{
if [[ $lastComplianceScan == "No scans have been run" ]];then
/bin/echo "no report to run, please run new scan"
pause
@@ -738,7 +734,7 @@ generate_stats(){{
count=($(compliance_count))
compliant=${{count[1]}}
non_compliant=${{count[2]}}
total=$((non_compliant + compliant))
percentage=$(printf %.2f $(( compliant * 100. / total )) )
/bin/echo "PASSED: $compliant FAILED: $non_compliant, $percentage percent compliant!"
@@ -773,22 +769,22 @@ fi
logging.debug(f"{rule}")
rule_yaml = get_rule_yaml(rule, custom)
if rule_yaml['id'].startswith("supplemental"):
continue
arch=""
if "tags" in rule_yaml:
try:
if "manual" in rule_yaml['tags']:
continue
if "arm64" in rule_yaml['tags']:
arch="arm64"
elif "i386" in rule_yaml['tags']:
arch="i386"
else:
arch=""
except:
pass
# grab the 800-53 controls
try:
rule_yaml['references']['800-53r5']
@@ -796,14 +792,14 @@ fi
nist_80053r5 = 'N/A'
else:
nist_80053r5 = rule_yaml['references']['800-53r5']
if reference == "default":
log_reference_id = [rule_yaml['id']]
else:
try:
try:
rule_yaml['references'][reference]
except KeyError:
try:
try:
rule_yaml['references']['custom'][reference]
except KeyError:
log_reference_id = [rule_yaml['id']]
@@ -817,8 +813,8 @@ fi
log_reference_id = rule_yaml['references'][reference] + [rule_yaml['id']]
else:
log_reference_id = [rule_yaml['references'][reference]] + [rule_yaml['id']]
# group the controls
if not nist_80053r5 == "N/A":
nist_80053r5.sort()
@@ -865,16 +861,16 @@ if [[ "$arch" == "$rule_arch" ]] || [[ -z "$rule_arch" ]]; then
# check to see if rule is exempt
unset exempt
unset exempt_reason
exempt=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt"]
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt"]
EOS
)
exempt_reason=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt_reason"]
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt_reason"]
EOS
)
if [[ $result_value == "{4}" ]]; then
/bin/echo "$(date -u) {5} passed (Result: $result_value, Expected: "{3}")" | /usr/bin/tee -a "$audit_log"
/usr/bin/defaults write "$audit_plist" {0} -dict-add finding -bool NO
@@ -891,8 +887,8 @@ EOS
/bin/sleep 1
fi
fi
else
/bin/echo "$(date -u) {5} does not apply to this architechture" | tee -a "$audit_log"
/usr/bin/defaults write "$audit_plist" {0} -dict-add finding -bool NO
@@ -922,12 +918,12 @@ unset exempt
unset exempt_reason
exempt=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt"]
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt"]
EOS
)
exempt_reason=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt_reason"]
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt_reason"]
EOS
)
@@ -969,7 +965,7 @@ if [[ ! -e "$audit_plist" ]]; then
pause
show_menus
read_options
else
else
exit 1
fi
fi
@@ -986,7 +982,7 @@ fi
# append to existing logfile
/bin/echo "$(date -u) Beginning remediation of non-compliant settings" >> "$audit_log"
# run mcxrefresh
# run mcxrefresh
/usr/bin/mcxrefresh -u $CURR_USER_UID
@@ -1000,16 +996,12 @@ fi
zparseopts -D -E -check=check -fix=fix -stats=stats -compliant=compliant -non_compliant=non_compliant
if [[ $check ]];then
run_scan
elif [[ $fix ]];then
run_fix
elif [[ $stats ]];then
generate_stats
elif [[ $compliant ]];then
compliance_count "compliant"
elif [[ $non_compliant ]];then
compliance_count "non-compliant"
if [[ $check ]] || [[ $fix ]] || [[ $stats ]] || [[ $compliant ]] || [[ $non_compliant ]]; then
if [[ $fix ]]; then run_fix; fi
if [[ $check ]]; then run_scan; fi
if [[ $stats ]];then generate_stats; fi
if [[ $compliant ]];then compliance_count "compliant"; fi
if [[ $non_compliant ]];then compliance_count "non-compliant"; fi
else
while true; do
show_menus
@@ -1033,19 +1025,19 @@ fi
#fix_script_file.close()
compliance_script_file.close()
def fill_in_odv(resulting_yaml, baseline_name):
def fill_in_odv(resulting_yaml, parent_values):
fields_to_process = ['title', 'discussion', 'check', 'fix']
_has_odv = False
if "odv" in resulting_yaml:
try:
odv = str(resulting_yaml['odv'][baseline_name])
odv = str(resulting_yaml['odv'][parent_values])
_has_odv = True
except KeyError:
try:
odv = str(resulting_yaml['odv']['custom'])
_has_odv = True
except KeyError:
odv = str(resulting_yaml['odv']['default'])
odv = str(resulting_yaml['odv']['recommended'])
_has_odv = True
else:
pass
@@ -1054,23 +1046,25 @@ def fill_in_odv(resulting_yaml, baseline_name):
for field in fields_to_process:
if "$ODV" in resulting_yaml[field]:
resulting_yaml[field]=resulting_yaml[field].replace("$ODV", odv)
for result_value in resulting_yaml['result']:
resulting_yaml['result'][result_value] = odv
if "$ODV" in str(resulting_yaml['result'][result_value]):
resulting_yaml['result'][result_value] = odv
if resulting_yaml['mobileconfig_info']:
for mobileconfig_type in resulting_yaml['mobileconfig_info']:
if isinstance(resulting_yaml['mobileconfig_info'][mobileconfig_type], dict):
for mobileconfig_value in resulting_yaml['mobileconfig_info'][mobileconfig_type]:
resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = odv
def get_rule_yaml(rule_file, custom=False, baseline_name=""):
if "$ODV" in str(resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value]):
resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = odv
def get_rule_yaml(rule_file, custom=False, parent_values="recommended"):
""" Takes a rule file, checks for a custom version, and returns the yaml for the rule
"""
global resulting_yaml
global resulting_yaml
resulting_yaml = {}
names = [os.path.basename(x) for x in glob.glob('../custom/rules/**/*.yaml', recursive=True)]
file_name = os.path.basename(rule_file)
@@ -1086,14 +1080,14 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""):
else:
with open(rule_file) as r:
rule_yaml = yaml.load(r, Loader=yaml.SafeLoader)
try:
og_rule_path = glob.glob('../rules/**/{}'.format(file_name), recursive=True)[0]
except IndexError:
#assume this is a completely new rule
og_rule_path = glob.glob('../custom/rules/**/{}'.format(file_name), recursive=True)[0]
resulting_yaml['customized'] = ["customized rule"]
# get original/default rule yaml for comparison
with open(og_rule_path) as og:
og_rule_yaml = yaml.load(og, Loader=yaml.SafeLoader)
@@ -1115,7 +1109,7 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""):
resulting_yaml['references'][ref] = rule_yaml['references'][ref]
except KeyError:
resulting_yaml['references'][ref] = og_rule_yaml['references'][ref]
try:
try:
if "custom" in rule_yaml['references']:
resulting_yaml['references']['custom'] = rule_yaml['references']['custom']
if 'customized' in resulting_yaml:
@@ -1127,14 +1121,16 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""):
pass
elif yaml_field == "tags":
# try to concatenate tags from both original yaml and custom yaml
if "tags" in rule_yaml:
try:
if og_rule_yaml["tags"] == rule_yaml["tags"]:
#print("using default data in yaml field {}".format("tags"))
resulting_yaml['tags'] = og_rule_yaml['tags']
#print("using default data in yaml field {}".format("tags"))
resulting_yaml['tags'] = og_rule_yaml['tags']
else:
#print("Found custom tags... concatenating them")
resulting_yaml['tags'] = og_rule_yaml['tags'] + rule_yaml['tags']
else:
except KeyError:
resulting_yaml['tags'] = og_rule_yaml['tags']
else:
try:
if og_rule_yaml[yaml_field] == rule_yaml[yaml_field]:
#print("using default data in yaml field {}".format(yaml_field))
@@ -1148,8 +1144,8 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""):
resulting_yaml['customized'] = ["customized {}".format(yaml_field)]
except KeyError:
resulting_yaml[yaml_field] = og_rule_yaml[yaml_field]
fill_in_odv(resulting_yaml, baseline_name)
fill_in_odv(resulting_yaml, parent_values)
return resulting_yaml
@@ -1190,7 +1186,7 @@ def generate_xls(baseline_name, build_path, baseline_yaml):
sheet1.write(0, 10, "SRG", headers)
sheet1.write(0, 11, "DISA STIG", headers)
sheet1.write(0, 12, "CIS Benchmark", headers)
sheet1.write(0, 13, "CIS v8", headers)
sheet1.write(0, 13, "CIS v8", headers)
sheet1.write(0, 14, "CCI", headers)
sheet1.write(0, 15, "Modifed Rule", headers)
sheet1.set_panes_frozen(True)
@@ -1201,7 +1197,7 @@ def generate_xls(baseline_name, build_path, baseline_yaml):
for rule in baseline_rules:
if rule.rule_id.startswith("supplemental") or rule.rule_id.startswith("srg"):
continue
sheet1.write(counter, 0, rule.rule_cce, top)
sheet1.col(0).width = 256 * 15
sheet1.write(counter, 1, rule.rule_id, top)
@@ -1298,12 +1294,12 @@ def generate_xls(baseline_name, build_path, baseline_yaml):
if title not in custom_ref_column:
custom_ref_column[title] = column_counter
column_counter = column_counter + 1
sheet1.write(0, custom_ref_column[title], title, headers)
sheet1.write(0, custom_ref_column[title], title, headers)
sheet1.col(custom_ref_column[title]).width = 512 * 25
added_ref = (str(ref)).strip('[]\'')
added_ref = added_ref.replace(", ", "\n").replace("\'", "")
sheet1.write(counter, custom_ref_column[title], added_ref, topWrap)
tall_style = xlwt.easyxf('font:height 640;') # 36pt
@@ -1426,7 +1422,7 @@ def is_asciidoctor_installed():
cmd = "which asciidoctor"
process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
output, error = process.communicate()
# return path to asciidoctor
return output.decode("utf-8").strip()
@@ -1447,7 +1443,7 @@ def verify_signing_hash(hash):
with tempfile.NamedTemporaryFile(mode="w") as in_file:
unsigned_tmp_file_path=in_file.name
in_file.write("temporary file for signing")
cmd = f"security cms -S -Z {hash} -i {unsigned_tmp_file_path}"
FNULL = open(os.devnull, 'w')
process = subprocess.Popen(cmd.split(), stdout=FNULL, stderr=FNULL)
@@ -1456,7 +1452,7 @@ def verify_signing_hash(hash):
return True
else:
return False
def sign_config_profile(in_file, out_file, hash):
"""Signs the configuration profile using the identity associated with the provided hash
"""
@@ -1539,28 +1535,32 @@ def main():
log_reference = args.reference
else:
log_reference = "default"
use_custom_reference = False
use_custom_reference = False
except IOError as msg:
parser.error(str(msg))
baseline_yaml = yaml.load(args.baseline, Loader=yaml.SafeLoader)
try:
parent_values = baseline_yaml['parent_values']
except KeyError:
parent_values = "recommended"
version_file = os.path.join(parent_dir, "VERSION.yaml")
with open(version_file) as r:
version_yaml = yaml.load(r, Loader=yaml.SafeLoader)
adoc_templates = [ "adoc_rule",
"adoc_supplemental",
"adoc_rule_no_setting",
adoc_templates = [ "adoc_rule",
"adoc_supplemental",
"adoc_rule_no_setting",
"adoc_rule_custom_refs",
"adoc_section",
"adoc_header",
"adoc_footer",
"adoc_section",
"adoc_header",
"adoc_footer",
"adoc_foreword",
"adoc_scope",
"adoc_authors",
"adoc_acronyms",
"adoc_scope",
"adoc_authors",
"adoc_acronyms",
"adoc_additional_docs"
]
adoc_templates_dict = {}
@@ -1572,7 +1572,7 @@ def main():
adoc_templates_dict[template] = f"../custom/templates/{template}.adoc"
else:
adoc_templates_dict[template] = f"../templates/{template}.adoc"
# check for custom PDF theme (must have theme in the name and end with .yml)
pdf_theme="mscp-theme.yml"
themes = glob.glob('../custom/templates/*theme*.yml')
@@ -1581,7 +1581,7 @@ def main():
elif len(themes) == 1 :
print(f"Found custom PDF theme: {themes[0]}")
pdf_theme = themes[0]
# Setup AsciiDoc templates
@@ -1593,7 +1593,7 @@ def main():
with open(adoc_templates_dict['adoc_rule_no_setting']) as adoc_rule_no_setting_file:
adoc_rule_no_setting_template = Template(adoc_rule_no_setting_file.read())
with open(adoc_templates_dict['adoc_rule_custom_refs']) as adoc_rule_custom_refs_file:
adoc_rule_custom_refs_template = Template(adoc_rule_custom_refs_file.read())
@@ -1605,13 +1605,13 @@ def main():
with open(adoc_templates_dict['adoc_footer']) as adoc_footer_file:
adoc_footer_template = Template(adoc_footer_file.read())
with open(adoc_templates_dict['adoc_foreword']) as adoc_foreword_file:
adoc_foreword_template = adoc_foreword_file.read() + "\n"
with open(adoc_templates_dict['adoc_scope']) as adoc_scope_file:
adoc_scope_template = Template(adoc_scope_file.read() +"\n")
with open(adoc_templates_dict['adoc_authors']) as adoc_authors_file:
adoc_authors_template = Template(adoc_authors_file.read() + "\n")
@@ -1636,7 +1636,7 @@ def main():
adoc_171_show=":show_171:"
else:
adoc_171_show=":show_171!:"
if args.gary:
adoc_tag_show=":show_tags:"
adoc_STIG_show=":show_STIG:"
@@ -1683,7 +1683,7 @@ def main():
adoc_output_file.write(adoc_acronyms_template)
adoc_output_file.write(adoc_additional_docs_template)
# Create sections and rules
for sections in baseline_yaml['profile']:
@@ -1730,8 +1730,8 @@ def main():
else:
rule_location = rule_path[0]
custom=False
rule_yaml = get_rule_yaml(rule_location, custom, baseline_name)
rule_yaml = get_rule_yaml(rule_location, custom, parent_values)
# Determine if the references exist and set accordingly
try:
@@ -1754,7 +1754,7 @@ def main():
nist_80053r5 = 'N/A'
else:
nist_80053r5 = rule_yaml['references']['800-53r5']
try:
rule_yaml['references']['800-171r2']
except KeyError:
@@ -1908,16 +1908,16 @@ def main():
# Output footer
adoc_output_file.write(footer_adoc)
adoc_output_file.close()
if args.profiles:
print("Generating configuration profiles...")
generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, signing, args.hash)
if args.script:
print("Generating compliance script...")
generate_script(baseline_name, build_path, baseline_yaml, log_reference)
default_audit_plist(baseline_name, build_path, baseline_yaml)
if args.xls:
print('Generating excel document...')
generate_xls(baseline_name, build_path, baseline_yaml)
@@ -1930,7 +1930,7 @@ def main():
process.communicate()
else:
print("If you would like to generate the HTML file from the AsciiDoc file, install the ruby gem for asciidoctor")
asciidoctorPDF_path = is_asciidoctor_pdf_installed()
# Don't create PDF if we are generating SCAP

View File

@@ -184,9 +184,9 @@ def generate_scap(all_rules, all_baselines, args):
loop = 1
if "odv" in og_rule_yaml:
loop = len(og_rule_yaml['odv'])
if args.baseline:
loop = 1
if args.baseline != "None":
loop = 1
for a in range(0, loop):
@@ -196,7 +196,7 @@ def generate_scap(all_rules, all_baselines, args):
odv_label = list(rule_yaml['odv'].keys())[a]
if args.baseline:
if args.baseline != "None":
odv_label = args.baseline
if odv_label == "hint":
@@ -219,24 +219,24 @@ def generate_scap(all_rules, all_baselines, args):
for mobileconfig_type in rule_yaml['mobileconfig_info']:
if isinstance(rule_yaml['mobileconfig_info'][mobileconfig_type], dict):
for mobileconfig_value in rule_yaml['mobileconfig_info'][mobileconfig_type]:
if "$ODV" == rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value]:
rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value].replace("$ODV",odv_value)
if "$ODV" in str(resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value]):
resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value].replace("$ODV",odv_value)
except:
odv_label = "default"
odv_label = "recommended"
for baseline in all_baselines:
found_rules = []
for tag in rule_yaml['tags']:
if tag == baseline:
if odv_label != "default" and odv_label == tag:
if odv_label != "recommended" and odv_label == tag or odv_label == "custom":
if baseline in generated_baselines:
generated_baselines[baseline].append(rule_yaml['id'] + "_" + odv_label)
else:
generated_baselines[baseline] = [rule_yaml['id'] + "_" + odv_label]
continue
elif odv_label == "default":
elif odv_label == "recommended" or odv_label == "custom":
if "odv" in rule_yaml:
if baseline not in rule_yaml['odv']:
@@ -2707,10 +2707,16 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""):
except:
pass
elif yaml_field == "tags":
if og_rule_yaml["tags"] == rule_yaml["tags"]:
# try to concatenate tags from both original yaml and custom yaml
try:
if og_rule_yaml["tags"] == rule_yaml["tags"]:
#print("using default data in yaml field {}".format("tags"))
resulting_yaml['tags'] = og_rule_yaml['tags']
else:
#print("Found custom tags... concatenating them")
resulting_yaml['tags'] = og_rule_yaml['tags'] + rule_yaml['tags']
except KeyError:
resulting_yaml['tags'] = og_rule_yaml['tags']
else:
resulting_yaml['tags'] = og_rule_yaml['tags'] + rule_yaml['tags']
else:
try:
if og_rule_yaml[yaml_field] == rule_yaml[yaml_field]: