diff --git a/docs/manual/developer_guide.adoc b/docs/manual/developer_guide.adoc index 6edabaaf7a1c..a642146de44d 100644 --- a/docs/manual/developer_guide.adoc +++ b/docs/manual/developer_guide.adoc @@ -1600,11 +1600,14 @@ audit_rules_usergroup_modification:: ** *path* - path that should be part of the audit rule as a value of `-w` argument, eg. `/etc/group`. * Languages: Ansible, Bash, OVAL -bls_bootloader_option:: -* Checks kernel command line arguments in BLS-compatible (Boot Loader Specification) boot loader configuration. +argument_value_in_line:: +* Checks that `argument=value` pair is present in (optionally) the line started with line_prefix (and, optionally, ending with line_suffix) in the file(s) defined by filepath. * Parameters: -** *arg_name* - argument name, eg. `audit` -** *arg_value* - argument value, eg. `'1'` +** *filepath* - File(s) to be checked. The value would be treated as a regular expression pattern. +** *arg_name* - Argument name, eg. `audit` +** *arg_value* - Argument value, eg. `'1'` +** *line_prefix* - The prefix of the line in which argument-value pair should be present, optional. +** *line_suffix* - The suffix of the line in which argument-value pair should be present, optional. * Languages: OVAL file_groupowner:: @@ -1812,8 +1815,6 @@ an OVAL template file called _template_OVAL_package_installed_: ---- -Notice that you can use Jinja macros and Jinja filters in the template code. - And here is the Ansible template file called _template_ANSIBLE_package_installed_: ---- @@ -1887,6 +1888,18 @@ def mount_option(data, lang): return data ---- +==== Filters + +You can use Jinja macros and Jinja filters in the template code. ComplianceAsCode support all built-in Jinja link:https://jinja.palletsprojects.com/en/2.11.x/templates/#builtin-filters[filters]. + +There are also some custom filters useful for content authoring defined in the project: + +escape_id:: +* Replaces all non-word (regex *\W*) characters with underscore. Useful for sanitizing ID strings as it is compatible with OVAL IDs `oval:[A-Za-z0-9_\-\.]+:ste:[1-9][0-9]*`. + +escape_regex:: +* Escapes characters in the string for it to be usable as a part of some regular expression, behaves similar to the Python 3's link:https://docs.python.org/3/library/re.html#re.escape[*re.escape*]. + === Tests (ctest) diff --git a/linux_os/guide/system/bootloader-grub2/grub2_admin_username/oval/shared.xml b/linux_os/guide/system/bootloader-grub2/grub2_admin_username/oval/shared.xml index c93ad9b88884..5857e7ad9ef9 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_admin_username/oval/shared.xml +++ b/linux_os/guide/system/bootloader-grub2/grub2_admin_username/oval/shared.xml @@ -3,12 +3,12 @@ {{{ oval_metadata("The grub2 boot loader superuser should have a username that is hard to guess.") }}} - {{{ oval_file_absent_criterion("/boot/grub2/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent_criterion("/boot/grub2/grub.cfg") }}} - {{{ oval_file_absent("/boot/grub2/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent("/boot/grub2/grub.cfg") }}} diff --git a/linux_os/guide/system/bootloader-grub2/grub2_password/oval/shared.xml b/linux_os/guide/system/bootloader-grub2/grub2_password/oval/shared.xml index 657f259c0f42..05f72fb76f21 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_password/oval/shared.xml +++ b/linux_os/guide/system/bootloader-grub2/grub2_password/oval/shared.xml @@ -4,7 +4,7 @@ {{{ oval_metadata("The grub2 boot loader should have password protection enabled.") }}} - {{{ oval_file_absent_criterion(grub_cfg_prefix + "/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent_criterion(grub_cfg_prefix + "/grub.cfg") }}} @@ -15,7 +15,7 @@ - {{{ oval_file_absent(grub_cfg_prefix + "/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent(grub_cfg_prefix + "/grub.cfg") }}} diff --git a/linux_os/guide/system/bootloader-grub2/grub2_uefi_admin_username/oval/shared.xml b/linux_os/guide/system/bootloader-grub2/grub2_uefi_admin_username/oval/shared.xml index 6142c8cdc33d..2062201eb6cb 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_uefi_admin_username/oval/shared.xml +++ b/linux_os/guide/system/bootloader-grub2/grub2_uefi_admin_username/oval/shared.xml @@ -3,12 +3,12 @@ {{{ oval_metadata("The grub2 boot loader superuser should have a username that is hard to guess.") }}} - {{{ oval_file_absent_criterion("/boot/efi/EFI/redhat/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent_criterion("/boot/efi/EFI/redhat/grub.cfg") }}} - {{{ oval_file_absent("/boot/efi/EFI/redhat/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent("/boot/efi/EFI/redhat/grub.cfg") }}} diff --git a/linux_os/guide/system/bootloader-grub2/grub2_uefi_password/oval/shared.xml b/linux_os/guide/system/bootloader-grub2/grub2_uefi_password/oval/shared.xml index 6d2a69d2ca31..74214da219f6 100644 --- a/linux_os/guide/system/bootloader-grub2/grub2_uefi_password/oval/shared.xml +++ b/linux_os/guide/system/bootloader-grub2/grub2_uefi_password/oval/shared.xml @@ -9,7 +9,7 @@ {{{ oval_metadata("The UEFI grub2 boot loader should have password protection enabled.") }}} - {{{ oval_file_absent_criterion(grub_cfg_prefix + "/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent_criterion(grub_cfg_prefix + "/grub.cfg") }}} @@ -20,7 +20,7 @@ - {{{ oval_file_absent(grub_cfg_prefix + "/grub.cfg", rule_id + "_grub_cfg") }}} + {{{ oval_file_absent(grub_cfg_prefix + "/grub.cfg") }}} diff --git a/shared/macros-bash.jinja b/shared/macros-bash.jinja index f4ef85942089..ee48bec12d3e 100644 --- a/shared/macros-bash.jinja +++ b/shared/macros-bash.jinja @@ -614,3 +614,28 @@ for f in $( ls /etc/sudoers /etc/sudoers.d/* 2> /dev/null ) ; do fi done {{%- endmacro -%}} + +{{% macro bash_sssd_ldap_config(parameter, value) -%}} +SSSD_CONF="/etc/sssd/sssd.conf" +LDAP_REGEX='[[:space:]]*\[domain\/[^]]*]([^(\n)]*(\n)+)+?[[:space:]]*{{{ parameter }}}' +AD_REGEX='[[:space:]]*\[domain\/[^]]*]([^(\n)]*(\n)+)+?[[:space:]]*id_provider[[:space:]]*=[[:space:]]*((?i)ad)[[:space:]]*$' +DOMAIN_REGEX="[[:space:]]*\[domain\/[^]]*]" + +# Check if id_provider is not set to ad (Active Directory) which makes start_tls not applicable, note the -v option to invert the grep. +# Try to find [domain/..] and {{{ parameter }}} in sssd.conf, if it exists, set to '{{{ value }}}' +# if {{{ parameter }}} isn't here, add it +# if [domain/..] doesn't exist, add it here for default domain +if grep -qvzosP $AD_REGEX $SSSD_CONF; then + if grep -qzosP $LDAP_REGEX $SSSD_CONF; then + sed -i "s#{{{ parameter }}}[^(\n)]*#{{{ parameter }}} = {{{ value }}}#" $SSSD_CONF + elif grep -qs $DOMAIN_REGEX $SSSD_CONF; then + sed -i "/$DOMAIN_REGEX/a {{{ parameter }}} = {{{ value }}}" $SSSD_CONF + else + if test -f "$SSSD_CONF"; then + echo -e "[domain/default]\n{{{ parameter }}} = {{{ value }}}" >> $SSSD_CONF + else + echo "Config file '$SSSD_CONF' doesnt exist, not remediating, assuming non-applicability." >&2 + fi + fi +fi +{{%- endmacro %}} diff --git a/shared/macros-oval.jinja b/shared/macros-oval.jinja index 0e077cd8948f..f730b3c61df7 100644 --- a/shared/macros-oval.jinja +++ b/shared/macros-oval.jinja @@ -220,8 +220,8 @@ - filepath (String): Path to the file to be checked. - id of the test name - the test will be named test_ #}} -{{%- macro oval_file_absent_criterion(filepath, id) -%}} - +{{%- macro oval_file_absent_criterion(filepath) -%}} + {{%- endmacro %}} {{# @@ -230,12 +230,13 @@ - filepath (String): Path to the configuration file to be checked. - id of the test name - the test will be named test_, the respective object object_ etc. #}} -{{%- macro oval_file_absent(filepath, id) -%}} - - +{{%- macro oval_file_absent(filepath) -%}} + + - - {{{ filepath }}} + + ^{{{ filepath }}} {{%- endmacro %}} @@ -250,6 +251,69 @@ {{%- endmacro %}} +{{# + Macro to define the OVAL test to check if there is a line in file with a pair of argument=value (Criterion definition). + Parameters: + - filepath (String): Path to the file to be checked. + - name (String): Argument name + - value (String): Argument value + - application (String): The application which the configuration file is being checked. Can be any value and does not affect the actual OVAL check. +#}} +{{%- macro oval_argument_value_in_line_criterion(filepath, name, value, application='') -%}} +{{%- set name_value = name+"="+value -%}} + +{{%- endmacro -%}} + +{{# + Macro to define the OVAL test to check if there is a line in file with a pair of argument=value (Test definition). + Parameters: + - filepath (String): Path to the configuration file to be checked. The operation is "pattern match" + - name (String): Argument name + - value (String): Argument value + - line_prefix (String): The starting part of the line with the list of arguments, default is empty + - line_suffix (String): The ending part of the line with the list of arguments, default is empty +#}} +{{%- macro oval_argument_value_in_line_test(filepath, name, value, line_prefix='', line_suffix='') -%}} +{{%- set name_value = name+"="+value -%}} + + + + + + ^{{{ filepath }}} + ^{{{ line_prefix|escape_regex }}}(.*){{{ line_suffix|escape_regex }}}$ + 1 + + + ^(?:.*\s)?{{{ name_value|escape_regex }}}(?:\s.*)?$ + +{{%- endmacro -%}} + +{{# + Hight level macro to define the OVAL test to check if there is a line in file with a pair of argument=value. + Parameters: + - filepath (String): Path to the configuration file to be checked. + - name (String): Argument name + - value (String): Argument value + - line_prefix (String): The starting part of the line with the list of arguments, default is empty + - line_suffix (String): The ending part of the line with the list of arguments, default is empty + - application (String): The application which the configuration file is being checked. Can be any value and does not affect the actual OVAL check. +#}} +{{%- macro oval_argument_value_in_line(filepath, name, value, line_prefix='', line_suffix='') -%}} + + + {{{ oval_metadata("Ensure argument " + name + "=" + value + " is present"+ (" in the line starting with '" + line_prefix + "'" if line_prefix else "") + " in file(s) '" + filepath + "'") }}} + + {{{- oval_argument_value_in_line_criterion(filepath, name, value, application) }}} + + + {{{- oval_argument_value_in_line_test(filepath, name, value, line_prefix, line_suffix) }}} + +{{%- endmacro -%}} + {{# High level macro to check if a particular combination of parameter and value in the ssh daemon configuration file is set. This macro can take five parameters: @@ -534,29 +598,3 @@ {{{ description }}} {{%- endmacro %}} - - -{{% macro bash_sssd_ldap_config(parameter, value) -%}} -SSSD_CONF="/etc/sssd/sssd.conf" -LDAP_REGEX='[[:space:]]*\[domain\/[^]]*]([^(\n)]*(\n)+)+?[[:space:]]*{{{ parameter }}}' -AD_REGEX='[[:space:]]*\[domain\/[^]]*]([^(\n)]*(\n)+)+?[[:space:]]*id_provider[[:space:]]*=[[:space:]]*((?i)ad)[[:space:]]*$' -DOMAIN_REGEX="[[:space:]]*\[domain\/[^]]*]" - -# Check if id_provider is not set to ad (Active Directory) which makes start_tls not applicable, note the -v option to invert the grep. -# Try to find [domain/..] and {{{ parameter }}} in sssd.conf, if it exists, set to '{{{ value }}}' -# if {{{ parameter }}} isn't here, add it -# if [domain/..] doesn't exist, add it here for default domain -if grep -qvzosP $AD_REGEX $SSSD_CONF; then - if grep -qzosP $LDAP_REGEX $SSSD_CONF; then - sed -i "s#{{{ parameter }}}[^(\n)]*#{{{ parameter }}} = {{{ value }}}#" $SSSD_CONF - elif grep -qs $DOMAIN_REGEX $SSSD_CONF; then - sed -i "/$DOMAIN_REGEX/a {{{ parameter }}} = {{{ value }}}" $SSSD_CONF - else - if test -f "$SSSD_CONF"; then - echo -e "[domain/default]\n{{{ parameter }}} = {{{ value }}}" >> $SSSD_CONF - else - echo "Config file '$SSSD_CONF' doesnt exist, not remediating, assuming non-applicability." >&2 - fi - fi -fi -{{%- endmacro %}} diff --git a/shared/templates/template_ANSIBLE_zipl_bls_entries_option b/shared/templates/template_ANSIBLE_zipl_bls_entries_option index bccad2267c11..7e73d391deba 100644 --- a/shared/templates/template_ANSIBLE_zipl_bls_entries_option +++ b/shared/templates/template_ANSIBLE_zipl_bls_entries_option @@ -4,7 +4,7 @@ # complexity = medium # disruption = low -- name: "Ensure BLS boot entries options contain {{{ ARG_NAME_VALUE }}}" +- name: "Ensure BLS boot entries options contain {{{ ARG_NAME }}}={{{ ARG_VALUE }}}" block: - name: "Check how many boot entries exist " find: @@ -12,15 +12,15 @@ patterns: "*.conf" register: n_entries - - name: "Check how many boot entries set {{{ ARG_NAME_VALUE }}}" + - name: "Check how many boot entries set {{{ ARG_NAME }}}={{{ ARG_VALUE }}}" find: paths: "/boot/loader/entries/" - contains: "^options .*{{{ ARG_NAME_VALUE }}}.*$" + contains: "^options .*{{{ ARG_NAME }}}={{{ ARG_VALUE }}}.*$" patterns: "*.conf" register: n_entries_options - name: "Update boot entries options" - command: grubby --update-kernel=ALL --args="{{{ ARG_NAME_VALUE }}}" + command: grubby --update-kernel=ALL --args="{{{ ARG_NAME }}}={{{ ARG_VALUE }}}" when: n_entries is defined and n_entries_options is defined and n_entries.matched != n_entries_options.matched - name: "Check if /etc/kernel/cmdline exists" @@ -28,25 +28,25 @@ path: /etc/kernel/cmdline register: cmdline_stat - - name: "Check if /etc/kernel/cmdline contains {{{ ARG_NAME_VALUE }}}" + - name: "Check if /etc/kernel/cmdline contains {{{ ARG_NAME }}}={{{ ARG_VALUE }}}" find: paths: "/etc/kernel/" patterns: "cmdline" - contains: "^.*{{{ ARG_NAME_VALUE }}}.*$" + contains: "^.*{{{ ARG_NAME }}}={{{ ARG_VALUE }}}.*$" register: cmdline_find - - name: "Add /etc/kernel/cmdline contains {{{ ARG_NAME_VALUE }}}" + - name: "Add /etc/kernel/cmdline contains {{{ ARG_NAME }}}={{{ ARG_VALUE }}}" lineinfile: create: yes path: "/etc/kernel/cmdline" - line: '{{{ ARG_NAME_VALUE }}}' + line: '{{{ ARG_NAME }}}={{{ ARG_VALUE }}}' when: cmdline_stat is defined and not cmdline_stat.stat.exists - - name: "Append /etc/kernel/cmdline contains {{{ ARG_NAME_VALUE }}}" + - name: "Append /etc/kernel/cmdline contains {{{ ARG_NAME }}}={{{ ARG_VALUE }}}" lineinfile: path: "/etc/kernel/cmdline" backrefs: yes regexp: "^(.*)$" - line: '\1 {{{ ARG_NAME_VALUE }}}' + line: '\1 {{{ ARG_NAME }}}={{{ ARG_VALUE }}}' when: cmdline_stat is defined and cmdline_stat.stat.exists and cmdline_find is defined and cmdline_find.matched == 0 diff --git a/shared/templates/template_BASH_zipl_bls_entries_option b/shared/templates/template_BASH_zipl_bls_entries_option index dde8c948f7a9..d0faeb805f55 100644 --- a/shared/templates/template_BASH_zipl_bls_entries_option +++ b/shared/templates/template_BASH_zipl_bls_entries_option @@ -1,11 +1,11 @@ # platform = Red Hat Enterprise Linux 8 # Correct BLS option using grubby, which is a thin wrapper around BLS operations -grubby --update-kernel=ALL --args="{{{ ARG_NAME_VALUE }}}" +grubby --update-kernel=ALL --args="{{{ ARG_NAME }}}={{{ ARG_VALUE }}}" # Ensure new kernels and boot entries retain the boot option if [ ! -f /etc/kernel/cmdline ]; then - echo "{{{ ARG_NAME_VALUE }}}" >> /etc/kernel/cmdline -elif ! grep -q '^(.*\s)?{{{ ARG_NAME_VALUE }}}(\s.*)?$' /etc/kernel/cmdline; then - sed -Ei 's/^(.*)$/\1 {{{ ARG_NAME_VALUE }}}/' /etc/kernel/cmdline + echo "{{{ ARG_NAME }}}={{{ ARG_VALUE }}}" > /etc/kernel/cmdline +elif ! grep -q '^(.*\s)?{{{ ARG_NAME }}}={{{ ARG_VALUE }}}(\s.*)?$' /etc/kernel/cmdline; then + sed -Ei 's/^(.*)$/\1 {{{ ARG_NAME }}}={{{ ARG_VALUE }}}/' /etc/kernel/cmdline fi diff --git a/shared/templates/template_OVAL_argument_in_file b/shared/templates/template_OVAL_argument_in_file new file mode 100644 index 000000000000..14cf33e7c8cd --- /dev/null +++ b/shared/templates/template_OVAL_argument_in_file @@ -0,0 +1,9 @@ +{{{ + oval_argument_value_in_line( + filepath=FILEPATH, + name=ARG_NAME, + value=ARG_VALUE, + line_prefix=LINE_PREFIX, + line_suffix=LINE_SUFFIX + ) +}}} diff --git a/shared/templates/template_OVAL_bls_entries_option b/shared/templates/template_OVAL_bls_entries_option deleted file mode 100644 index d60f13c8f9c1..000000000000 --- a/shared/templates/template_OVAL_bls_entries_option +++ /dev/null @@ -1,28 +0,0 @@ - - - {{{ oval_metadata("Ensure " + ARG_NAME_VALUE + " option is configured in the 'options' line in /boot/loader/entries/*.conf.") }}} - - - - - - - - - - - - ^/boot/loader/entries/.*\.conf$ - ^options (.*)$ - 1 - - - - ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ - - diff --git a/shared/templates/template_OVAL_coreos_kernel_option b/shared/templates/template_OVAL_coreos_kernel_option index 9a161ba17368..63fbd7a490fe 100644 --- a/shared/templates/template_OVAL_coreos_kernel_option +++ b/shared/templates/template_OVAL_coreos_kernel_option @@ -1,71 +1,26 @@ - - Ensure that the most recent (default) CoreOS boot loader entry is configured to run Linux operating system with argument {{{ ARG_NAME_VALUE }}} - {{{- oval_affected(products) }}} - Ensure {{{ ARG_NAME_VALUE }}} option is configured in the 'options' line in /boot/loader/entries/ostree-2-*.conf (or ostree-1-*.conf if the second version does not exists). - - + {{{ oval_metadata("Ensure " + ARG_NAME + "=" + ARG_VALUE +" argument is present in the 'options' line of /boot/loader/entries/ostree-2-*.conf (or ostree-1-*.conf if there is no ostree-2-*.conf as ostree has only two enries at the most, with *-2-*.conf entry always being the most recent). Also, ensure that kernel is currently running with this argument by checking /proc/cmdline.") }}} + + - + {{{- oval_file_absent_criterion('/boot/loader/entries/ostree-2-.*.conf')}}} + {{{- oval_argument_value_in_line_criterion('/boot/loader/entries/ostree-1-.*.conf', ARG_NAME, ARG_VALUE, 'Linux kernel') }}} - - + {{{- oval_argument_value_in_line_criterion('/boot/loader/entries/ostree-2-.*.conf', ARG_NAME, ARG_VALUE, 'Linux kernel') }}} - + + + {{{- oval_argument_value_in_line_criterion('/proc/cmdline', ARG_NAME, ARG_VALUE, 'Linux kernel') }}} + + - - - - + {{{- oval_file_absent('/boot/loader/entries/ostree-2-.*.conf') }}} + {{{- oval_argument_value_in_line_test('/boot/loader/entries/ostree-1-.*.conf', ARG_NAME, ARG_VALUE, 'options ') }}} - - ^/boot/loader/entries/ostree-2-*\.conf$ - ^options (.*)$ - 1 - - - - ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ - - - - - - - - - ^/boot/loader/entries/ostree-1-*\.conf$ - ^options (.*)$ - 1 - - - - ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ - - - - - - - - ^/boot/loader/entries/ostree-2-*\.conf - + {{{- oval_argument_value_in_line_test('/boot/loader/entries/ostree-2-.*.conf', ARG_NAME, ARG_VALUE, 'options ') }}} + {{{- oval_argument_value_in_line_test('/proc/cmdline', ARG_NAME, ARG_VALUE, 'BOOT_IMAGE') }}} diff --git a/shared/templates/template_OVAL_zipl_bls_entries_option b/shared/templates/template_OVAL_zipl_bls_entries_option index 8b786450f093..905078e28efd 100644 --- a/shared/templates/template_OVAL_zipl_bls_entries_option +++ b/shared/templates/template_OVAL_zipl_bls_entries_option @@ -1,43 +1,12 @@ - {{{ oval_metadata("Ensure " + ARG_NAME_VALUE + " option is configured in the 'options' line in /boot/loader/entries/*.conf.") }}} + {{{ oval_metadata("Ensure " + ARG_NAME + "=" + ARG_VALUE + " option is configured in the 'options' line in /boot/loader/entries/*.conf. Make sure that newly installed kernels will retain this option, it should be configured in /etc/kernel/cmdline as well.") }}} - - + {{{- oval_argument_value_in_line_criterion('/boot/loader/entries/.*.conf', ARG_NAME, ARG_VALUE, 'Linux kernel') }}} + {{{- oval_argument_value_in_line_criterion('/etc/kernel/cmdline', ARG_NAME, ARG_VALUE, 'Linux kernel') }}} - - - - - - - ^/boot/loader/entries/.*\.conf$ - ^options (.*)$ - 1 - - - - - - - - /etc/kernel/cmdline - ^(.*)$ - 1 - - - - ^(?:.*\s)?{{{ ESCAPED_ARG_NAME_VALUE }}}(?:\s.*)?$ - + {{{- oval_argument_value_in_line_test('/boot/loader/entries/.*.conf', ARG_NAME, ARG_VALUE, 'options ') }}} + {{{- oval_argument_value_in_line_test('/etc/kernel/cmdline', ARG_NAME, ARG_VALUE) }}} diff --git a/ssg/jinja.py b/ssg/jinja.py index e81ee4ce5232..a514f91c9a6f 100644 --- a/ssg/jinja.py +++ b/ssg/jinja.py @@ -23,7 +23,9 @@ name_to_platform, prodtype_to_platform, banner_regexify, - banner_anchor_wrap + banner_anchor_wrap, + escape_id, + escape_regex ) @@ -87,6 +89,8 @@ def _get_jinja_environment(substitutions_dict): ) _get_jinja_environment.env.filters['banner_regexify'] = banner_regexify _get_jinja_environment.env.filters['banner_anchor_wrap'] = banner_anchor_wrap + _get_jinja_environment.env.filters['escape_regex'] = escape_regex + _get_jinja_environment.env.filters['escape_id'] = escape_id return _get_jinja_environment.env diff --git a/ssg/templates.py b/ssg/templates.py index e238ed76a09c..5543fcadaac1 100644 --- a/ssg/templates.py +++ b/ssg/templates.py @@ -7,6 +7,7 @@ from xml.sax.saxutils import unescape import ssg.build_yaml +import ssg.utils try: from urllib.parse import quote @@ -25,8 +26,6 @@ "kubernetes": ".yml" } -def sanitize_input(string): - return re.sub(r'[\W_]', '_', string) templates = dict() @@ -73,7 +72,7 @@ def audit_rules_file_deletion_events(data, lang): @template(["ansible", "bash", "oval", "kubernetes"]) def audit_rules_login_events(data, lang): path = data["path"] - name = re.sub(r'[-\./]', '_', os.path.basename(os.path.normpath(path))) + name = ssg.utils.escape_id(os.path.basename(os.path.normpath(path))) data["name"] = name if lang == "oval": data["path"] = path.replace("/", "\\/") @@ -83,7 +82,7 @@ def audit_rules_login_events(data, lang): @template(["ansible", "bash", "oval"]) def audit_rules_path_syscall(data, lang): if lang == "oval": - pathid = re.sub(r'[-\./]', '_', data["path"]) + pathid = ssg.utils.escape_id(data["path"]) # remove root slash made into '_' pathid = pathid[1:] data["pathid"] = pathid @@ -93,7 +92,7 @@ def audit_rules_path_syscall(data, lang): @template(["ansible", "bash", "oval", "kubernetes"]) def audit_rules_privileged_commands(data, lang): path = data["path"] - name = re.sub(r"[-\./]", "_", os.path.basename(path)) + name = ssg.utils.escape_id(os.path.basename(path)) data["name"] = name if lang == "oval": data["id"] = data["_rule_id"] @@ -134,7 +133,7 @@ def audit_rules_unsuccessful_file_modification_rule_order(data, lang): @template(["ansible", "bash", "oval"]) def audit_rules_usergroup_modification(data, lang): path = data["path"] - name = re.sub(r'[-\./]', '_', os.path.basename(path)) + name = ssg.utils.escape_id(os.path.basename(path)) data["name"] = name if lang == "oval": data["path"] = path.replace("/", "\\/") @@ -144,7 +143,7 @@ def audit_rules_usergroup_modification(data, lang): @template(["ansible", "bash", "oval"]) def audit_file_contents(data, lang): if lang == "oval": - pathid = re.sub(r'[-\./]', '_', data["filepath"]) + pathid = ssg.utils.escape_id(data["filepath"]) # remove root slash made into '_' pathid = pathid[1:] data["filepath_id"] = pathid @@ -216,7 +215,7 @@ def grub2_bootloader_argument(data, lang): # escape dot, this is used in oval regex data["escaped_arg_name_value"] = data["arg_name_value"].replace(".", "\\.") # replace . with _, this is used in test / object / state ids - data["sanitized_arg_name"] = data["arg_name"].replace(".", "_") + data["sanitized_arg_name"] = ssg.utils.escape_id(data["arg_name"]) return data @@ -227,13 +226,13 @@ def kernel_module_disabled(data, lang): @template(["anaconda", "oval"]) def mount(data, lang): - data["pointid"] = re.sub(r'[-\./]', '_', data["mountpoint"]) + data["pointid"] = ssg.utils.escape_id(data["mountpoint"]) return data def _mount_option(data, lang): if lang == "oval": - data["pointid"] = re.sub(r"[-\./]", "_", data["mountpoint"]).lstrip("_") + data["pointid"] = ssg.utils.escape_id(data["mountpoint"]) else: data["mountoption"] = re.sub(" ", ",", data["mountoption"]) return data @@ -247,7 +246,7 @@ def mount_option(data, lang): @template(["ansible", "bash", "oval"]) def mount_option_remote_filesystems(data, lang): if lang == "oval": - data["mountoptionid"] = sanitize_input(data["mountoption"]) + data["mountoptionid"] = ssg.utils.escape_id(data["mountoption"]) return _mount_option(data, lang) @@ -270,7 +269,7 @@ def package_installed(data, lang): @template(["ansible", "bash", "oval"]) def sysctl(data, lang): - data["sysctlid"] = re.sub(r'[-\.]', '_', data["sysctlvar"]) + data["sysctlid"] = ssg.utils.escape_id(data["sysctlvar"]) if not data.get("sysctlval"): data["sysctlval"] = "" ipv6_flag = "P" @@ -365,24 +364,18 @@ def yamlfile_value(data, lang): @template(["oval"]) -def bls_entries_option(data, lang): - data["arg_name_value"] = data["arg_name"] + "=" + data["arg_value"] - if lang == "oval": - # escape dot, this is used in oval regex - data["escaped_arg_name_value"] = data["arg_name_value"].replace(".", "\\.") - # replace . with _, this is used in test / object / state ids - data["sanitized_arg_name"] = data["arg_name"].replace(".", "_") +def argument_value_in_line(data, lang): return data @template(["ansible", "bash", "oval"]) def zipl_bls_entries_option(data, lang): - return bls_entries_option(data, lang) + return data @template(["oval"]) def coreos_kernel_option(data, lang): - return bls_entries_option(data, lang) + return data class Builder(object): diff --git a/ssg/utils.py b/ssg/utils.py index f59445b327a4..a347c1b94104 100644 --- a/ssg/utils.py +++ b/ssg/utils.py @@ -252,15 +252,27 @@ def mkdir_p(path): raise -def banner_regexify(banner_text): +def escape_regex(text): # We could use re.escape(), but it escapes too many characters, including plain white space. # In python 3.7 the set of charaters escaped by re.escape is reasonable, so lets mimic it. # See https://docs.python.org/3/library/re.html#re.sub # '!', '"', '%', "'", ',', '/', ':', ';', '<', '=', '>', '@', and "`" are not escaped. - banner_text = re.sub(r"([#$&*+-.^`|~:()])", r"\\\1", banner_text) - banner_text = banner_text.replace("\n", "BFLMPSVZ") - banner_text = banner_text.replace(" ", "[\\s\\n]+") - return banner_text.replace("BFLMPSVZ", "(?:[\\n]+|(?:\\\\n)+)") + return re.sub(r"([#$&*+-.^`|~:()])", r"\\\1", text) + + +def escape_id(text): + # Make a string used as an Id for OSCAP/XCCDF/OVAL entities more readable + # and compatible with: + # OVAL: r'oval:[A-Za-z0-9_\-\.]+:ste:[1-9][0-9]*' + return re.sub(r"[^\w]+", "_", text).strip("_") + + +def banner_regexify(banner_text): + return escape_regex(banner_text) \ + .replace("\n", "BFLMPSVZ") \ + .replace(" ", "[\\s\\n]+") \ + .replace("BFLMPSVZ", "(?:[\\n]+|(?:\\\\n)+)") + def banner_anchor_wrap(banner_text): return "^" + banner_text + "$"