بنقرة واحدة
بنقرة واحدة
Create a template for checks and remediations
Create a new product in ComplianceAsCode project
Search for existing rules that match a given requirement text. Identify rules that implement a specific control.
Create or update a versioned profile pair (versioned + unversioned extends pattern).
Build a ComplianceAsCode product
Create a new security rule with all required components
| name | create-test-scenarios |
| description | Create Automatus test scenarios to test the given rule. |
| argument-hint | <rule_id> |
Create test scenarios for execution by the Automatus test framework. Automatus and test scenarios are documented in tests/README.md.
Scope: This skill covers Linux rules only. It does not cover OpenShift tests (e.g., tests in ocp4 directories).
Goal: Produce a summary of key information about the rule.
rule.yml file. The directory name matches the rule ID.rule.yml.
description and rationale fields to identify what to test.template key in rule.yml)mcp__content-agent__get_rendered_rule(product, rule_id) to see the actual OVAL check and remediation code after Jinja2 expansion. This is the most important tool for understanding what to test -- the raw rule.yml does not show final file paths, config keys, or values after template expansion.mcp__content-agent__get_rule_product_availability(rule_id) to see which products include the rule.tests/ subdirectory.shared/templates/${template_name}/tests.tests/shared/ to discover reusable Bash helper scripts (see "Shared test helpers" below).shared/macros/20-test-scenarios.jinja to discover available Jinja macros for test setup (see "Jinja macros for test scenarios" below).MCP tools to use:
mcp__content-agent__get_rule_details(rule_id) -- quickly identifies template name and metadatamcp__content-agent__get_rendered_rule(product, rule_id) -- shows the actual OVAL check and remediation code after Jinja2 expansionmcp__content-agent__search_rendered_content(query, product, limit) -- searches rendered build artifacts (useful for finding actual values after template expansion)mcp__content-agent__get_template_schema(template_name) -- for templated rules, shows parameter schemaGoal: Produce a design document describing the test scenarios to create.
Use the information gathered in Phase 1 to propose test scenarios.
Propose a set of "pass" and "fail" test scenarios:
.fail.sh scenario must put the system in a state that is both detectable as non-compliant by the OVAL check and fixable by the remediation.
Do not create "notapplicable" test scenarios. They are valid but serve specialized purposes outside this skill's scope.
Pass and fail scenarios for the same rule typically share roughly 80% of their setup code. Fail scenarios differ from pass scenarios by:
Design pass/fail pairs together to ensure consistency.
Every rule must have at least 1 pass and at least 1 fail test scenario. A typical rule has 5 to 10 test scenarios, but the exact number depends on the rule's complexity.
Suggested distribution:
.d directories): 2-3 pass, 4-5 fail.d directories (test with and without files in the .d directory).d file when only the main file is checked, or vice versa)Focus on edge cases when designing test scenarios.
Goal: Produce executable Bash test scenario files based on the Phase 2 design.
Every scenario file must start with #!/bin/bash.
Use Jinja2 macros and expressions in scenario files. This project uses non-standard delimiters:
{{{ }}} for expressions{{% %}} for control flowUse product properties such as {{{ grub2_boot_path }}} and {{{ audit_watches_style }}} to parametrize test scenarios across products.
Use product-specific conditionals when different products require different setup:
{{% if product in ["rhel9", "rhel10"] %}}
grubby --update-kernel=ALL --args="audit=1"
{{% elif "ubuntu" in product %}}
sed -i 's/GRUB_CMDLINE_LINUX="/&audit=1 /' /etc/default/grub
update-grub
{{% else %}}
grub2-editenv - set "$(grub2-editenv - list | grep kernelopts) audit=1"
{{% endif %}}
Additional Jinja macros for test scenarios are available in shared/macros/20-test-scenarios.jinja (see section below).
Each test scenario file begins with a comment header (lines starting with #). Include only the headers that apply:
packages -- packages that must be installed before the test runsplatform -- restricts the scenario to specific platformscheck -- use only when the rule has both OVAL and SCE checks but the scenario works with only one check engineremediation -- set to none when the rule has no remediation or when remediation would break the test environmentvariables -- sets XCCDF Value parameters; format is variable_name=actual_value (use the actual value, not a selector name)profiles -- use only for profile-specific regression tests where the rule is not parametrized by an XCCDF ValueNon-templated rules: Place test scenario files in the tests/ subdirectory of the rule directory. Create the directory if it does not exist.
Templated rules: Determine whether the scenario is specific to a single rule or can be parametrized and reused by other rules using the same template. Reusable scenarios are far more common.
tests/ subdirectory.shared/templates/${template_name}/tests/.In reusable test scenarios, use template parameters instead of specific values. Call mcp__content-agent__get_template_schema() to get the list of available parameters. Template parameters are substituted by Jinja. Write template parameter names in CAPITAL letters.
test_config.ymlA rule can include tests/test_config.yml to control which templated scenarios run. This is important when some templated tests do not apply to a specific rule. The file supports Jinja2 and is product-aware.
Use deny_templated_scenarios to block specific templated scenarios:
deny_templated_scenarios:
- wrong_runtime.fail.sh
- missing_config.fail.sh
Use allow_templated_scenarios to allow only specific templated scenarios (all others are blocked). Use - none to disable all templated scenarios for the rule:
allow_templated_scenarios:
- none
Source helper scripts from tests/shared/ using the $SHARED variable (e.g., . $SHARED/partition.sh).
| Script | Functions | Use for |
|---|---|---|
partition.sh | create_partition(), mount_partition(path), clean_up_partition(path), make_fstab_given_partition_line(mountpoint, fstype, options), make_fstab_correct_partition_line(mountpoint) | Mount option tests |
dconf_test_functions.sh | clean_dconf_settings(), add_dconf_setting(path, setting, value, db, file), add_dconf_lock(path, setting, db, file) | GNOME/dconf tests |
utilities.sh | assert_directive_in_file(file, directive_start, full_directive) | Config file directive management |
utils.sh | set_parameters_value(file, param, value), delete_parameter(file, param) | Key-value config file tests |
accounts_common.sh | run_foreach_noninteractive_shell_account() | Account restriction tests |
grub2.sh | Grub2 environment helpers | Bootloader tests |
rsyslog_log_utils.sh | create_rsyslog_test_logs() | Rsyslog tests |
audit_rules_watch/ | Reusable audit watch scenarios (12 files, auditctl/augenrules variants) | Audit watch rule tests -- source these instead of recreating them |
Macros from shared/macros/20-test-scenarios.jinja:
| Macro | Purpose |
|---|---|
setup_auditctl_environment() | Configure audit service to use auditctl for rule loading (product-aware) |
setup_augenrules_environment() | Configure audit service to use augenrules (product-aware) |
tests_init_faillock_vars(state, prm_name, ext_variable, lower_bound, upper_bound) | Initialize faillock test variables for correct, stricter, lenient_high, or lenient_low states |
setup_rsyslog_common() | Set up rsyslog environment variables (RSYSLOG_CONF, RSYSLOG_D_FOLDER, RSYSLOG_D_CONF) |
remove_rsyslog_entry(pattern) | Remove matching lines from rsyslog config files |
remove_rsyslog_legacy_entry(legacy_parameter) | Remove legacy-format rsyslog entries (starting with $) |
remove_rsyslog_rainerscript_block_entry(block_type, pattern) | Remove RainerScript blocks containing a pattern |
setup_rsyslog_remote_loghost(loghost_line) | Set up remote loghost in rsyslog.conf |
Specialized composition macros for specific rsyslog rules also exist (e.g., setup_rsyslog_encrypt_offload_actionsendstreamdriverauthmode()).
.d directories./etc/sysctl.conf or /etc/sysctl.d/) and the runtime value (sysctl -w key=value)./etc/sysctl.conf, /etc/sysctl.d/*, /usr/lib/sysctl.d/*, /run/sysctl.d/*.sed -i '/pattern/d' to remove existing config lines before adding new ones to prevent duplicate entries..pass.sh.fail.shwrong_value.fail.sh)#!/bin/bash
# packages = openssh-server
# platform = multi_platform_rhel
# Set the sshd option to a wrong value so the rule fails
sed -i '/KerberosAuthentication/d' /etc/ssh/sshd_config
echo "KerberosAuthentication yes" >> /etc/ssh/sshd_config
correct.pass.sh)#!/bin/bash
# platform = Ubuntu 24.04
getent group "adm" &>/dev/null || groupadd adm
mkdir -p /var/log/apt
touch /var/log/apt/testfile
chgrp adm /var/log/apt/testfile
augenrules_correct_extra_permission.pass.sh)This example demonstrates:
packages header to ensure the audit RPM package is installedplatform header limiting applicability to RHEL products (rhel8, rhel9, rhel10)variables header setting an actual value (not a selector) for the XCCDF Value var_accounts_passwords_pam_faillock_dirsetup_auditctl_environment() generating Bash codeaudit_watches_style substituted into the code$SHARED variable#!/bin/bash
# packages = audit
# platform = multi_platform_rhel
# variables = var_accounts_passwords_pam_faillock_dir=/var/log/faillock
{{{ setup_auditctl_environment() }}}
path="/var/log/faillock"
style="{{{ audit_watches_style }}}"
filter_type="path"
. $SHARED/audit_rules_watch/auditctl_correct_without_key.pass.sh
service_enabled template (service_disabled.fail.sh)Template parameters are substituted by Jinja and must be written in CAPITAL letters.
#!/bin/bash
{{% if SERVICENAME in ["ssh", "sshd"] %}}
# platform = Not Applicable
{{% endif %}}
# packages = {{{ PACKAGENAME }}}
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop '{{{ DAEMONNAME }}}.service'
"$SYSTEMCTL_EXEC" disable '{{{ DAEMONNAME }}}.service'
correct_value.pass.sh)Demonstrates proper cleanup of all sysctl config locations and setting both config and runtime values:
#!/bin/bash
# variables = sysctl_{{{ SYSCTLID }}}_value={{{ SYSCTL_CORRECT_VALUE }}}
rm -rf /usr/lib/sysctl.d/* /run/sysctl.d/* /etc/sysctl.d/*
sed -i "/{{{ SYSCTLVAR }}}/d" /etc/sysctl.conf
echo "{{{ SYSCTLVAR }}} = {{{ SYSCTL_CORRECT_VALUE }}}" >> /etc/sysctl.conf
sysctl -w {{{ SYSCTLVAR }}}="{{{ SYSCTL_CORRECT_VALUE }}}"
fstab.fail.sh)#!/bin/bash
# platform = multi_platform_all
. $SHARED/partition.sh
clean_up_partition {{{ MOUNTPOINT }}}
create_partition
make_fstab_given_partition_line {{{ MOUNTPOINT }}} ext2 nodev
mount_partition {{{ MOUNTPOINT }}} || true
correct_value.pass.sh)#!/bin/bash
# packages = dconf,gdm
. $SHARED/dconf_test_functions.sh
clean_dconf_settings
add_dconf_setting "{{{ SECTION }}}" "{{{ PARAMETER }}}" "{{{ VALUE }}}" "{{{ DCONF_DATABASE_DIRECTORY }}}" "00-security-settings"
add_dconf_lock "{{{ SECTION }}}" "{{{ PARAMETER }}}" "{{{ DCONF_DATABASE_DIRECTORY }}}" "00-security-settings-lock"
dconf update
Run the created test scenarios using Automatus. Use the test-rule skill. Verify that all test scenarios pass. Run tests with both oscap and ansible remediations. Don't run the test with the bash remediations.
For fail test scenarios, don't check only the initial scan results, watch for failures in the scan after remediation.
The rule returned the expected result pass result during the initial scan.
INFO - Script banner_etc_issue_disa_dod_short.pass.sh using profile (all) OK
The rule didn't return the expected result pass result during the initial scan, instead it returned the fail result.
ERROR - Script banner_etc_issue_disa_dod_short.pass.sh using profile (all) found issue:
ERROR - Rule evaluation resulted in fail, instead of expected pass during initial stage
ERROR - The initial scan failed for rule 'xccdf_org.ssgproject.content_rule_banner_etc_issue'.
OpenSCAP evaluated the rule as fail during the first run, which was expected by the test scenario. Then, the remediation was executed, and after the remediation, the rule was evaluated as pass so oscap returned the fixed result in the second run.
INFO - Script banner_etc_issue_disa_dod_short.fail.sh using profile (all) OK
A "fail" test scenario returned the expected fail results in the initial scan. Then, it executed the remediation, but after the remediation the rule was evaluated as fail again, which means that the scanner reported the error result for the rule.
This signalizes that the remediation for this rule doesn't work correctly.
INFO - Script banner_etc_issue_disa_dod_short.fail.sh using profile (all) OK
ERROR - Rule evaluation resulted in error, instead of expected fixed during remediation stage
ERROR - The remediation failed for rule 'xccdf_org.ssgproject.content_rule_banner_etc_issue'.
.d directories before setting up the test stateprofiles header when the variables header should be used insteadvariables header (e.g., # variables = var_name=selector instead of # variables = var_name=10){{ }}, {% %}) instead of the project's custom syntax ({{{ }}}, {{% %}})#!/bin/bash)sysctl -w after editing sysctl.conf)$SHARED