Blame SOURCES/scap-security-guide-0.1.59-multifile_templates-PR_7405.patch

ff1465
commit 26f72c842ec184ed517fbf0d3224c421ad7cc9c6
ff1465
Author: Gabriel Becker <ggasparb@redhat.com>
ff1465
Date:   Thu Feb 24 18:33:50 2022 +0100
ff1465
ff1465
    Manual edited patch scap-security-guide-0.1.59-multifile_templates-PR_7405.patch.
ff1465
ff1465
diff --git a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/ansible/shared.yml b/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/ansible/shared.yml
ff1465
deleted file mode 100644
ff1465
index f6f2ab4..0000000
ff1465
--- a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/ansible/shared.yml
ff1465
+++ /dev/null
ff1465
@@ -1,25 +0,0 @@
ff1465
-# platform = multi_platform_sle,Red Hat Enterprise Linux 8,multi_platform_fedora
ff1465
-# reboot = false
ff1465
-# strategy = restrict
ff1465
-# complexity = medium
ff1465
-# disruption = medium
ff1465
-- name: "Read list libraries without root ownership"
ff1465
-  find:
ff1465
-    paths:
ff1465
-      - "/usr/lib"
ff1465
-      - "/usr/lib64"
ff1465
-      - "/lib"
ff1465
-      - "/lib64"
ff1465
-    file_type: "directory"
ff1465
-  register: library_dirs_not_group_owned_by_root
ff1465
-
ff1465
-- name: "Set group ownership of system library dirs to root"
ff1465
-  file:
ff1465
-    path: "{{ item.path }}"
ff1465
-    group: "root"
ff1465
-    state: "directory"
ff1465
-    mode: "{{ item.mode }}"
ff1465
-  with_items: "{{ library_dirs_not_group_owned_by_root.files }}"
ff1465
-  when:
ff1465
-    - library_dirs_not_group_owned_by_root.matched > 0
ff1465
-    - item.gid != 0
ff1465
diff --git a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/bash/shared.sh b/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/bash/shared.sh
ff1465
deleted file mode 100644
ff1465
index 365b983..0000000
ff1465
--- a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/bash/shared.sh
ff1465
+++ /dev/null
ff1465
@@ -1,7 +0,0 @@
ff1465
-# platform = multi_platform_sle,Red Hat Enterprise Linux 8,multi_platform_fedora
ff1465
-
ff1465
-find /lib \
ff1465
-/lib64 \
ff1465
-/usr/lib \
ff1465
-/usr/lib64 \
ff1465
-\! -group root -type d -exec chgrp root '{}' \;
ff1465
diff --git a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/oval/shared.xml b/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/oval/shared.xml
ff1465
deleted file mode 100644
ff1465
index 3af60ff..0000000
ff1465
--- a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/oval/shared.xml
ff1465
+++ /dev/null
ff1465
@@ -1,27 +0,0 @@
ff1465
-<def-group>
ff1465
-  <definition class="compliance" id="dir_group_ownership_library_dirs" version="1">
ff1465
-    {{{ oval_metadata("
ff1465
-        Checks that /lib, /lib64, /usr/lib, /usr/lib64, /lib/modules, and
ff1465
-        directories therein, are group-owned by root.
ff1465
-      ") }}}
ff1465
-    <criteria operator="AND">
ff1465
-      <criterion test_ref="test_dir_group_ownership_lib_dir" />
ff1465
-    </criteria>
ff1465
-  </definition>
ff1465
-
ff1465
-  <unix:file_test  check="all" check_existence="none_exist" comment="library directories gid root" id="test_dir_group_ownership_lib_dir" version="1">
ff1465
-    <unix:object object_ref="object_dir_group_ownership_lib_dir" />
ff1465
-  </unix:file_test>
ff1465
-
ff1465
-  <unix:file_object comment="library directories" id="object_dir_group_ownership_lib_dir" version="1">
ff1465
-    
ff1465
-    <unix:path operation="pattern match">(^\/lib(|64)\/|^\/usr\/lib(|64)\/)</unix:path>
ff1465
-    <unix:filename xsi:nil="true" />
ff1465
-    <filter action="include">state_group_owner_library_dirs_not_root</filter>
ff1465
-  </unix:file_object>
ff1465
-
ff1465
-  <unix:file_state id="state_group_owner_library_dirs_not_root" version="1">
ff1465
-    <unix:group_id datatype="int" operation="not equal">0</unix:group_id>
ff1465
-  </unix:file_state>
ff1465
-
ff1465
-</def-group>
ff1465
diff --git a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/rule.yml b/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/rule.yml
ff1465
index 8c0acc0..10203c9 100644
ff1465
--- a/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/rule.yml
ff1465
+++ b/linux_os/guide/system/permissions/files/permissions_within_important_dirs/dir_group_ownership_library_dirs/rule.yml
ff1465
@@ -1,6 +1,6 @@
ff1465
 documentation_complete: true
ff1465
 
ff1465
-prodtype: sle12,sle15,rhel8,fedora
ff1465
+prodtype: fedora,rhel8,sle12,sle15,ubuntu2004
ff1465
 
ff1465
 title: 'Verify that Shared Library Directories Have Root Group Ownership'
ff1465
 
ff1465
@@ -40,6 +40,7 @@ references:
ff1465
     stigid@rhel8: RHEL-08-010350
ff1465
     stigid@sle12: SLES-12-010876
ff1465
     stigid@sle15: SLES-15-010356
ff1465
+    stigid@ubuntu2004: UBTU-20-010431
ff1465
 
ff1465
 ocil_clause: 'any of these directories are not group-owned by root'
ff1465
 
ff1465
@@ -52,3 +53,14 @@ ocil: |-
ff1465
     For each of these directories, run the following command to find files not
ff1465
     owned by root:
ff1465
     
$ sudo find -L $DIR ! -user root -type d -exec chgrp root {} \;
ff1465
+
ff1465
+template:
ff1465
+    name: file_groupowner
ff1465
+    vars:
ff1465
+        filepath:
ff1465
+            - /lib/
ff1465
+            - /lib64/
ff1465
+            - /usr/lib/
ff1465
+            - /usr/lib64/
ff1465
+        recursive: 'true'
ff1465
+        filegid: '0'
ff1465
diff --git a/products/ubuntu2004/profiles/stig.profile b/products/ubuntu2004/profiles/stig.profile
ff1465
index ac96858..4c76824 100644
ff1465
--- a/products/ubuntu2004/profiles/stig.profile
ff1465
+++ b/products/ubuntu2004/profiles/stig.profile
ff1465
@@ -470,6 +470,7 @@ selections:
ff1465
     # UBTU-20-010430 The Ubuntu operating system library files must be group-owned by root.
ff1465
 
ff1465
     # UBTU-20-010431 The Ubuntu operating system library directories must be group-owned by root.
ff1465
+    - dir_group_ownership_library_dirs
ff1465
 
ff1465
     # UBTU-20-010432 The Ubuntu operating system must be configured to preserve log records from failure events.
ff1465
     - service_rsyslog_enabled
ff1465
diff --git a/shared/templates/file_groupowner/ansible.template b/shared/templates/file_groupowner/ansible.template
ff1465
index 073d356..68fc2e1 100644
ff1465
--- a/shared/templates/file_groupowner/ansible.template
ff1465
+++ b/shared/templates/file_groupowner/ansible.template
ff1465
@@ -4,33 +4,44 @@
ff1465
 # complexity = low
ff1465
 # disruption = low
ff1465
 
ff1465
+{{% for path in FILEPATH %}}
ff1465
 {{% if IS_DIRECTORY and FILE_REGEX %}}
ff1465
 
ff1465
-- name: Find {{{ FILEPATH }}} file(s) matching {{{ FILE_REGEX }}}
ff1465
+- name: Find {{{ path }}} file(s) matching {{{ FILE_REGEX[loop.index0] }}}
ff1465
   find:
ff1465
-    paths: "{{{ FILEPATH }}}"
ff1465
-    patterns: "{{{ FILE_REGEX }}}"
ff1465
+    paths: "{{{ path }}}"
ff1465
+    patterns: {{{ FILE_REGEX[loop.index0] }}}
ff1465
     use_regex: yes
ff1465
   register: files_found
ff1465
 
ff1465
-- name: Ensure group owner on {{{ FILEPATH }}} file(s) matching {{{ FILE_REGEX }}}
ff1465
+- name: Ensure group owner on {{{ path }}} file(s) matching {{{ FILE_REGEX[loop.index0] }}}
ff1465
   file:
ff1465
     path: "{{ item.path }}"
ff1465
     group: "{{{ FILEGID }}}"
ff1465
   with_items:
ff1465
     - "{{ files_found.files }}"
ff1465
 
ff1465
+{{% elif IS_DIRECTORY and RECURSIVE %}}
ff1465
+
ff1465
+- name: Ensure group owner on {{{ path }}} recursively
ff1465
+  file:
ff1465
+    path: "{{{ path }}}"
ff1465
+    state: directory
ff1465
+    recurse: yes
ff1465
+    group: "{{{ FILEGID }}}"
ff1465
+
ff1465
 {{% else %}}
ff1465
 
ff1465
-- name: Test for existence {{{ FILEPATH }}}
ff1465
+- name: Test for existence {{{ path }}}
ff1465
   stat:
ff1465
-    path: "{{{ FILEPATH }}}"
ff1465
+    path: "{{{ path }}}"
ff1465
   register: file_exists
ff1465
 
ff1465
-- name: Ensure group owner {{{ FILEGID }}} on {{{ FILEPATH }}}
ff1465
+- name: Ensure group owner {{{ FILEGID }}} on {{{ path }}}
ff1465
   file:
ff1465
-    path: "{{{ FILEPATH }}}"
ff1465
+    path: "{{{ path }}}"
ff1465
     group: "{{{ FILEGID }}}"
ff1465
   when: file_exists.stat is defined and file_exists.stat.exists
ff1465
 
ff1465
 {{% endif %}}
ff1465
+{{% endfor %}}
ff1465
diff --git a/shared/templates/file_groupowner/bash.template b/shared/templates/file_groupowner/bash.template
ff1465
index 442e015..982d2f3 100644
ff1465
--- a/shared/templates/file_groupowner/bash.template
ff1465
+++ b/shared/templates/file_groupowner/bash.template
ff1465
@@ -4,13 +4,17 @@
ff1465
 # complexity = low
ff1465
 # disruption = low
ff1465
 
ff1465
+{{% for path in FILEPATH %}}
ff1465
 {{% if IS_DIRECTORY and FILE_REGEX %}}
ff1465
-readarray -t files < <(find {{{ FILEPATH }}})
ff1465
+readarray -t files < <(find {{{ path }}})
ff1465
 for file in "${files[@]}"; do
ff1465
-    if basename $file | grep -q '{{{ FILE_REGEX }}}'; then
ff1465
+    if basename $file | grep -qE '{{{ FILE_REGEX[loop.index0] }}}'; then
ff1465
         chgrp {{{ FILEGID }}} $file
ff1465
     fi
ff1465
 done
ff1465
+{{% elif IS_DIRECTORY and RECURSIVE %}}
ff1465
+find -L {{{ path }}} -type d -exec chgrp {{{ FILEGID }}} {} \;
ff1465
 {{% else %}}
ff1465
-chgrp {{{ FILEGID }}} {{{ FILEPATH }}}
ff1465
+chgrp {{{ FILEGID }}} {{{ path }}}
ff1465
 {{% endif %}}
ff1465
+{{% endfor %}}
ff1465
diff --git a/shared/templates/file_groupowner/oval.template b/shared/templates/file_groupowner/oval.template
ff1465
index 1b637a6..fd2e5db 100644
ff1465
--- a/shared/templates/file_groupowner/oval.template
ff1465
+++ b/shared/templates/file_groupowner/oval.template
ff1465
@@ -1,8 +1,16 @@
ff1465
 <def-group>
ff1465
   <definition class="compliance" id="{{{ _RULE_ID }}}" version="1">
ff1465
+   {{% if FILEPATH is not string %}}
ff1465
+      {{{ oval_metadata("This test makes sure that FILEPATH is group owned by " + FILEGID + ".") }}}
ff1465
+      <criteria>
ff1465
+    {{% for filepath in FILEPATH %}}
ff1465
+      <criterion comment="Check file group ownership of {{{ filepath }}}" test_ref="test_file_groupowner{{{ FILEID }}}_{{{ loop.index0 }}}" />
ff1465
+    {{% endfor %}}
ff1465
+   {{% else %}}
ff1465
     {{{ oval_metadata("This test makes sure that " + FILEPATH + " is group owned by " + FILEGID + ".") }}}
ff1465
     <criteria>
ff1465
       <criterion comment="Check file group ownership of {{{ FILEPATH }}}" test_ref="test_file_groupowner{{{ FILEID }}}" />
ff1465
+   {{% endif %}}
ff1465
     </criteria>
ff1465
   </definition>
ff1465
   {{%- if MISSING_FILE_PASS -%}}
ff1465
@@ -12,23 +20,31 @@
ff1465
     {{# All defined files must exist. When using regex, at least one file must match #}}
ff1465
     {{% set FILE_EXISTENCE = "all_exist" %}}
ff1465
   {{%- endif -%}}
ff1465
-  <unix:file_test check="all" check_existence="{{{ FILE_EXISTENCE }}}" comment="Testing group ownership of {{{ FILEPATH }}}" id="test_file_groupowner{{{ FILEID }}}" version="1">
ff1465
-    <unix:object object_ref="object_file_groupowner{{{ FILEID }}}" />
ff1465
-    <unix:state state_ref="state_file_groupowner{{{ FILEID }}}_gid_{{{ FILEGID }}}" />
ff1465
+
ff1465
+
ff1465
+  {{% for filepath in FILEPATH %}}
ff1465
+  <unix:file_test check="all" check_existence="{{{ FILE_EXISTENCE }}}" comment="Testing group ownership of {{{ filepath }}}" id="test_file_groupowner{{{ FILEID }}}_{{{ loop.index0 }}}" version="1">
ff1465
+    <unix:object object_ref="object_file_groupowner{{{ FILEID }}}_{{{ loop.index0 }}}" />
ff1465
+    <unix:state state_ref="state_file_groupowner{{{ FILEID }}}_gid_{{{ FILEGID }}}_{{{ loop.index0 }}}" />
ff1465
   </unix:file_test>
ff1465
-  <unix:file_state id="state_file_groupowner{{{ FILEID }}}_gid_{{{ FILEGID }}}" version="1">
ff1465
+  <unix:file_state id="state_file_groupowner{{{ FILEID }}}_gid_{{{ FILEGID }}}_{{{ loop.index0 }}}" version="1">
ff1465
     <unix:group_id datatype="int">{{{ FILEGID }}}</unix:group_id>
ff1465
   </unix:file_state>
ff1465
-  <unix:file_object comment="{{{ FILEPATH }}}" id="object_file_groupowner{{{ FILEID }}}" version="1">
ff1465
+  <unix:file_object comment="{{{ filepath }}}" id="object_file_groupowner{{{ FILEID }}}_{{{ loop.index0 }}}" version="1">
ff1465
     {{%- if IS_DIRECTORY -%}}
ff1465
-      <unix:path>{{{ FILEPATH }}}</unix:path>
ff1465
-      {{%- if FILE_REGEX -%}}
ff1465
-        <unix:filename operation="pattern match">{{{ FILE_REGEX }}}</unix:filename>
ff1465
-      {{%- else -%}}
ff1465
-        <unix:filename xsi:nil="true" />
ff1465
-      {{%- endif -%}}
ff1465
-    {{%- else -%}}
ff1465
-      <unix:filepath{{% if FILEPATH_IS_REGEX %}} operation="pattern match"{{% endif %}}>{{{ FILEPATH }}}</unix:filepath>
ff1465
-    {{%- endif -%}}
ff1465
+      {{%- if FILE_REGEX %}}
ff1465
+      <unix:path>{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename operation="pattern match">{{{ FILE_REGEX[loop.index0] }}}</unix:filename>
ff1465
+      {{%- elif RECURSIVE %}}
ff1465
+      <unix:path operation="pattern match">{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename xsi:nil="true" />
ff1465
+      {{%- else %}}
ff1465
+      <unix:path>{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename xsi:nil="true" />
ff1465
+      {{%- endif %}}
ff1465
+    {{%- else %}}
ff1465
+      <unix:filepath{{% if FILEPATH_IS_REGEX %}} operation="pattern match"{{% endif %}}>{{{ filepath }}}</unix:filepath>
ff1465
+    {{%- endif %}}
ff1465
   </unix:file_object>
ff1465
+  {{% endfor %}}
ff1465
 </def-group>
ff1465
diff --git a/shared/templates/file_groupowner/template.py b/shared/templates/file_groupowner/template.py
ff1465
index 2263ae8..10baed9 100644
ff1465
--- a/shared/templates/file_groupowner/template.py
ff1465
+++ b/shared/templates/file_groupowner/template.py
ff1465
@@ -1,12 +1,25 @@
ff1465
-from ssg.utils import parse_template_boolean_value
ff1465
+from ssg.utils import parse_template_boolean_value, check_conflict_regex_directory
ff1465
 
ff1465
 def _file_owner_groupowner_permissions_regex(data):
ff1465
-    data["is_directory"] = data["filepath"].endswith("/")
ff1465
-    if "file_regex" in data and not data["is_directory"]:
ff1465
-        raise ValueError(
ff1465
-            "Used 'file_regex' key in rule '{0}' but filepath '{1}' does not "
ff1465
-            "specify a directory. Append '/' to the filepath or remove the "
ff1465
-            "'file_regex' key.".format(data["_rule_id"], data["filepath"]))
ff1465
+    # this avoids code duplicates
ff1465
+    if isinstance(data["filepath"], str):
ff1465
+        data["filepath"] = [data["filepath"]]
ff1465
+
ff1465
+    if "file_regex" in data:
ff1465
+        # we can have a list of filepaths, but only one regex
ff1465
+        # instead of declaring the same regex multiple times
ff1465
+        if isinstance(data["file_regex"], str):
ff1465
+            data["file_regex"] = [data["file_regex"]] * len(data["filepath"])
ff1465
+
ff1465
+        # if the length of filepaths and file_regex are not the same, then error.
ff1465
+        # in case we have multiple regexes for just one filepath, than we need
ff1465
+        # to declare that filepath multiple times
ff1465
+        if len(data["filepath"]) != len(data["file_regex"]):
ff1465
+            raise ValueError(
ff1465
+                "You should have one file_path per file_regex. Please check "
ff1465
+                "rule '{0}'".format(data["_rule_id"]))
ff1465
+
ff1465
+    check_conflict_regex_directory(data)
ff1465
 
ff1465
 
ff1465
 def preprocess(data, lang):
ff1465
@@ -14,6 +27,10 @@ def preprocess(data, lang):
ff1465
 
ff1465
     data["missing_file_pass"] = parse_template_boolean_value(data, parameter="missing_file_pass", default_value=False)
ff1465
 
ff1465
+    data["recursive"] = parse_template_boolean_value(data,
ff1465
+                                                     parameter="recursive",
ff1465
+                                                     default_value=False)
ff1465
+
ff1465
     if lang == "oval":
ff1465
         data["fileid"] = data["_rule_id"].replace("file_groupowner", "")
ff1465
     return data
ff1465
diff --git a/shared/templates/file_owner/ansible.template b/shared/templates/file_owner/ansible.template
ff1465
index 6083fbe..80eaae8 100644
ff1465
--- a/shared/templates/file_owner/ansible.template
ff1465
+++ b/shared/templates/file_owner/ansible.template
ff1465
@@ -4,33 +4,44 @@
ff1465
 # complexity = low
ff1465
 # disruption = low
ff1465
 
ff1465
+{{% for path in FILEPATH %}}
ff1465
 {{% if IS_DIRECTORY and FILE_REGEX %}}
ff1465
 
ff1465
-- name: Find {{{ FILEPATH }}} file(s) matching {{{ FILE_REGEX }}}
ff1465
+- name: Find {{{ path }}} file(s) matching {{{ FILE_REGEX[loop.index0] }}}
ff1465
   find:
ff1465
-    paths: "{{{ FILEPATH }}}"
ff1465
-    patterns: "{{{ FILE_REGEX }}}"
ff1465
+    paths: "{{{ path }}}"
ff1465
+    patterns: {{{ FILE_REGEX[loop.index0] }}}
ff1465
     use_regex: yes
ff1465
   register: files_found
ff1465
 
ff1465
-- name: Ensure group owner on {{{ FILEPATH }}} file(s) matching {{{ FILE_REGEX }}}
ff1465
+- name: Ensure group owner on {{{ path }}} file(s) matching {{{ FILE_REGEX[loop.index0] }}}
ff1465
   file:
ff1465
     path: "{{ item.path }}"
ff1465
     owner: "{{{ FILEUID }}}"
ff1465
   with_items:
ff1465
     - "{{ files_found.files }}"
ff1465
 
ff1465
+{{% elif IS_DIRECTORY and RECURSIVE %}}
ff1465
+
ff1465
+- name: Ensure owner on {{{ path }}} recursively
ff1465
+  file:
ff1465
+    paths "{{{ path }}}"
ff1465
+    state: directory
ff1465
+    recurse: yes
ff1465
+    owner: "{{{ FILEUID }}}"
ff1465
+
ff1465
 {{% else %}}
ff1465
 
ff1465
-- name: Test for existence {{{ FILEPATH }}}
ff1465
+- name: Test for existence {{{ path }}}
ff1465
   stat:
ff1465
-    path: "{{{ FILEPATH }}}"
ff1465
+    path: "{{{ path }}}"
ff1465
   register: file_exists
ff1465
 
ff1465
-- name: Ensure owner {{{ FILEUID }}} on {{{ FILEPATH }}}
ff1465
+- name: Ensure owner {{{ FILEUID }}} on {{{ path }}}
ff1465
   file:
ff1465
-    path: "{{{ FILEPATH }}}"
ff1465
+    path: "{{{ path }}}"
ff1465
     owner: "{{{ FILEUID }}}"
ff1465
   when: file_exists.stat is defined and file_exists.stat.exists
ff1465
 
ff1465
 {{% endif %}}
ff1465
+{{% endfor %}}
ff1465
diff --git a/shared/templates/file_owner/bash.template b/shared/templates/file_owner/bash.template
ff1465
index 16025b7..27b5a2a 100644
ff1465
--- a/shared/templates/file_owner/bash.template
ff1465
+++ b/shared/templates/file_owner/bash.template
ff1465
@@ -4,13 +4,17 @@
ff1465
 # complexity = low
ff1465
 # disruption = low
ff1465
 
ff1465
+{{% for path in FILEPATH %}}
ff1465
 {{% if IS_DIRECTORY and FILE_REGEX %}}
ff1465
-readarray -t files < <(find {{{ FILEPATH }}})
ff1465
+readarray -t files < <(find {{{ path }}})
ff1465
 for file in "${files[@]}"; do
ff1465
-    if basename $file | grep -q '{{{ FILE_REGEX }}}'; then
ff1465
+    if basename $file | grep -qE '{{{ FILE_REGEX[loop.index0] }}}'; then
ff1465
         chown {{{ FILEUID }}} $file
ff1465
     fi
ff1465
 done
ff1465
+{{% elif IS_DIRECTORY and RECURSIVE %}}
ff1465
+find -L {{{ path }}} -type d -exec chown {{{ FILEUID }}} {} \;
ff1465
 {{% else %}}
ff1465
-chown {{{ FILEUID }}} {{{ FILEPATH }}}
ff1465
+chown {{{ FILEUID }}} {{{ path }}}
ff1465
 {{% endif %}}
ff1465
+{{% endfor %}}
ff1465
diff --git a/shared/templates/file_owner/oval.template b/shared/templates/file_owner/oval.template
ff1465
index 23ac161..105e29c 100644
ff1465
--- a/shared/templates/file_owner/oval.template
ff1465
+++ b/shared/templates/file_owner/oval.template
ff1465
@@ -1,8 +1,16 @@
ff1465
 <def-group>
ff1465
   <definition class="compliance" id="{{{ _RULE_ID }}}" version="1">
ff1465
+  {{% if FILEPATH is not string %}}
ff1465
+    {{{ oval_metadata("This test makes sure that FILEPATH is owned by " + FILEUID + ".") }}}
ff1465
+     <criteria>
ff1465
+   {{% for filepath in FILEPATH %}}
ff1465
+     <criterion comment="Check file ownership of {{{ filepath }}}" test_ref="test_file_owner{{{ FILEID }}}_{{{ loop.index0 }}}" />
ff1465
+   {{% endfor %}}
ff1465
+  {{% else %}}
ff1465
     {{{ oval_metadata("This test makes sure that " + FILEPATH + " is owned by " + FILEUID + ".") }}}
ff1465
     <criteria>
ff1465
       <criterion comment="Check file ownership of {{{ FILEPATH }}}" test_ref="test_file_owner{{{ FILEID }}}" />
ff1465
+  {{% endif %}}
ff1465
     </criteria>
ff1465
   </definition>
ff1465
   {{%- if MISSING_FILE_PASS -%}}
ff1465
@@ -12,23 +20,30 @@
ff1465
     {{# All defined files must exist. When using regex, at least one file must match #}}
ff1465
     {{% set FILE_EXISTENCE = "all_exist" %}}
ff1465
   {{%- endif -%}}
ff1465
-  <unix:file_test check="all" check_existence="{{{ FILE_EXISTENCE }}}" comment="Testing user ownership of {{{ FILEPATH }}}" id="test_file_owner{{{ FILEID }}}" version="1">
ff1465
-    <unix:object object_ref="object_file_owner{{{ FILEID }}}" />
ff1465
-    <unix:state state_ref="state_file_owner{{{ FILEID }}}_uid_{{{ FILEUID }}}" />
ff1465
+
ff1465
+  {{% for filepath in FILEPATH %}}
ff1465
+  <unix:file_test check="all" check_existence="{{{ FILE_EXISTENCE }}}" comment="Testing user ownership of {{{ filepath }}}" id="test_file_owner{{{ FILEID }}}_{{{ loop.index0 }}}" version="1">
ff1465
+    <unix:object object_ref="object_file_owner{{{ FILEID }}}_{{{ loop.index0 }}}" />
ff1465
+    <unix:state state_ref="state_file_owner{{{ FILEID }}}_uid_{{{ FILEUID }}}_{{{ loop.index0 }}}" />
ff1465
   </unix:file_test>
ff1465
-  <unix:file_state id="state_file_owner{{{ FILEID }}}_uid_{{{ FILEUID }}}" version="1">
ff1465
+  <unix:file_state id="state_file_owner{{{ FILEID }}}_uid_{{{ FILEUID }}}_{{{ loop.index0 }}}" version="1">
ff1465
     <unix:user_id datatype="int">{{{ FILEUID }}}</unix:user_id>
ff1465
   </unix:file_state>
ff1465
-  <unix:file_object comment="{{{ FILEPATH }}}" id="object_file_owner{{{ FILEID }}}" version="1">
ff1465
+  <unix:file_object comment="{{{ filepath }}}" id="object_file_owner{{{ FILEID }}}_{{{ loop.index0 }}}" version="1">
ff1465
     {{%- if IS_DIRECTORY -%}}
ff1465
-      <unix:path>{{{ FILEPATH }}}</unix:path>
ff1465
-      {{%- if FILE_REGEX -%}}
ff1465
-        <unix:filename operation="pattern match">{{{ FILE_REGEX }}}</unix:filename>
ff1465
-      {{%- else -%}}
ff1465
-        <unix:filename xsi:nil="true" />
ff1465
-      {{%- endif -%}}
ff1465
-    {{%- else -%}}
ff1465
-      <unix:filepath{{% if FILEPATH_IS_REGEX %}} operation="pattern match"{{% endif %}}>{{{ FILEPATH }}}</unix:filepath>
ff1465
-    {{%- endif -%}}
ff1465
+      {{%- if FILE_REGEX %}}
ff1465
+      <unix:path>{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename operation="pattern match">{{{ FILE_REGEX[loop.index0] }}}</unix:filename>
ff1465
+      {{%- elif RECURSIVE %}}
ff1465
+      <unix:path operation="pattern match">{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename xsi:nil="true" />
ff1465
+      {{%- else %}}
ff1465
+      <unix:path>{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename xsi:nil="true" />
ff1465
+      {{%- endif %}}
ff1465
+    {{%- else %}}
ff1465
+      <unix:filepath{{% if FILEPATH_IS_REGEX %}} operation="pattern match"{{% endif %}}>{{{ filepath }}}</unix:filepath>
ff1465
+    {{%- endif %}}
ff1465
   </unix:file_object>
ff1465
+  {{% endfor %}}
ff1465
 </def-group>
ff1465
diff --git a/shared/templates/file_owner/template.py b/shared/templates/file_owner/template.py
ff1465
index 0dd0008..1391dcf 100644
ff1465
--- a/shared/templates/file_owner/template.py
ff1465
+++ b/shared/templates/file_owner/template.py
ff1465
@@ -1,12 +1,25 @@
ff1465
-from ssg.utils import parse_template_boolean_value
ff1465
+from ssg.utils import parse_template_boolean_value, check_conflict_regex_directory
ff1465
 
ff1465
 def _file_owner_groupowner_permissions_regex(data):
ff1465
-    data["is_directory"] = data["filepath"].endswith("/")
ff1465
-    if "file_regex" in data and not data["is_directory"]:
ff1465
-        raise ValueError(
ff1465
-            "Used 'file_regex' key in rule '{0}' but filepath '{1}' does not "
ff1465
-            "specify a directory. Append '/' to the filepath or remove the "
ff1465
-            "'file_regex' key.".format(data["_rule_id"], data["filepath"]))
ff1465
+    # this avoids code duplicates
ff1465
+    if isinstance(data["filepath"], str):
ff1465
+        data["filepath"] = [data["filepath"]]
ff1465
+
ff1465
+    if "file_regex" in data:
ff1465
+        # we can have a list of filepaths, but only one regex
ff1465
+        # instead of declaring the same regex multiple times
ff1465
+        if isinstance(data["file_regex"], str):
ff1465
+            data["file_regex"] = [data["file_regex"]] * len(data["filepath"])
ff1465
+
ff1465
+        # if the length of filepaths and file_regex are not the same, then error.
ff1465
+        # in case we have multiple regexes for just one filepath, than we need
ff1465
+        # to declare that filepath multiple times
ff1465
+        if len(data["filepath"]) != len(data["file_regex"]):
ff1465
+            raise ValueError(
ff1465
+                "You should have one file_path per file_regex. Please check "
ff1465
+                "rule '{0}'".format(data["_rule_id"]))
ff1465
+
ff1465
+    check_conflict_regex_directory(data)
ff1465
 
ff1465
 
ff1465
 def preprocess(data, lang):
ff1465
@@ -14,6 +27,10 @@ def preprocess(data, lang):
ff1465
 
ff1465
     data["missing_file_pass"] = parse_template_boolean_value(data, parameter="missing_file_pass", default_value=False)
ff1465
 
ff1465
+    data["recursive"] = parse_template_boolean_value(data,
ff1465
+                                                     parameter="recursive",
ff1465
+                                                     default_value=False)
ff1465
+
ff1465
     if lang == "oval":
ff1465
         data["fileid"] = data["_rule_id"].replace("file_owner", "")
ff1465
     return data
ff1465
diff --git a/shared/templates/file_permissions/ansible.template b/shared/templates/file_permissions/ansible.template
ff1465
index 029d03f..fc211bd 100644
ff1465
--- a/shared/templates/file_permissions/ansible.template
ff1465
+++ b/shared/templates/file_permissions/ansible.template
ff1465
@@ -3,33 +3,45 @@
ff1465
 # strategy = configure
ff1465
 # complexity = low
ff1465
 # disruption = low
ff1465
+
ff1465
+{{% for path in FILEPATH %}}
ff1465
 {{% if IS_DIRECTORY and FILE_REGEX %}}
ff1465
 
ff1465
-- name: Find {{{ FILEPATH }}} file(s)
ff1465
+- name: Find {{{ path }}} file(s)
ff1465
   find:
ff1465
-    paths: "{{{ FILEPATH }}}"
ff1465
-    patterns: "{{{ FILE_REGEX }}}"
ff1465
+    paths: "{{{ path }}}"
ff1465
+    patterns: {{{ FILE_REGEX[loop.index0] }}}
ff1465
     use_regex: yes
ff1465
   register: files_found
ff1465
 
ff1465
-- name: Set permissions for {{{ FILEPATH }}} file(s)
ff1465
+- name: Set permissions for {{{ path }}} file(s)
ff1465
   file:
ff1465
     path: "{{ item.path }}"
ff1465
     mode: "{{{ FILEMODE }}}"
ff1465
   with_items:
ff1465
     - "{{ files_found.files }}"
ff1465
 
ff1465
+{{% elif IS_DIRECTORY and RECURSIVE %}}
ff1465
+
ff1465
+- name: Set permissions for {{{ path }}} recursively
ff1465
+  file:
ff1465
+    path: "{{{ path }}}"
ff1465
+    state: directory
ff1465
+    recurse: yes
ff1465
+    mode: "{{{ FILEMODE }}}"
ff1465
+
ff1465
 {{% else %}}
ff1465
 
ff1465
-- name: Test for existence {{{ FILEPATH }}}
ff1465
+- name: Test for existence {{{ path }}}
ff1465
   stat:
ff1465
-    path: "{{{ FILEPATH }}}"
ff1465
+    path: "{{{ path }}}"
ff1465
   register: file_exists
ff1465
   
ff1465
-- name: Ensure permission {{{ FILEMODE }}} on {{{ FILEPATH }}}
ff1465
+- name: Ensure permission {{{ FILEMODE }}} on {{{ path }}}
ff1465
   file:
ff1465
-    path: "{{{ FILEPATH }}}"
ff1465
+    path: "{{{ path }}}"
ff1465
     mode: "{{{ FILEMODE }}}"
ff1465
   when: file_exists.stat is defined and file_exists.stat.exists
ff1465
 
ff1465
 {{% endif %}}
ff1465
+{{% endfor %}}
ff1465
diff --git a/shared/templates/file_permissions/bash.template b/shared/templates/file_permissions/bash.template
ff1465
index af9cf4e..e0d8fe9 100644
ff1465
--- a/shared/templates/file_permissions/bash.template
ff1465
+++ b/shared/templates/file_permissions/bash.template
ff1465
@@ -4,13 +4,17 @@
ff1465
 # complexity = low
ff1465
 # disruption = low
ff1465
 
ff1465
+{{% for path in FILEPATH %}}
ff1465
 {{% if IS_DIRECTORY and FILE_REGEX %}}
ff1465
-readarray -t files < <(find {{{ FILEPATH }}})
ff1465
+readarray -t files < <(find {{{ path }}})
ff1465
 for file in "${files[@]}"; do
ff1465
-    if basename $file | grep -q '{{{ FILE_REGEX }}}'; then
ff1465
+    if basename $file | grep -qE '{{{ FILE_REGEX[loop.index0] }}}'; then
ff1465
         chmod {{{ FILEMODE }}} $file
ff1465
     fi    
ff1465
 done
ff1465
+{{% elif IS_DIRECTORY and RECURSIVE %}}
ff1465
+find -L {{{ path }}} -type d -exec chmod {{{ FILEMODE }}} {} \;
ff1465
 {{% else %}}
ff1465
-chmod {{{ FILEMODE }}} {{{ FILEPATH }}}
ff1465
+chmod {{{ FILEMODE }}} {{{ path }}}
ff1465
 {{% endif %}}
ff1465
+{{% endfor %}}
ff1465
diff --git a/shared/templates/file_permissions/oval.template b/shared/templates/file_permissions/oval.template
ff1465
index f570ff8..89083e8 100644
ff1465
--- a/shared/templates/file_permissions/oval.template
ff1465
+++ b/shared/templates/file_permissions/oval.template
ff1465
@@ -16,31 +16,47 @@
ff1465
 {{%- endif -%}}
ff1465
 <def-group>
ff1465
   <definition class="compliance" id="{{{ _RULE_ID }}}" version="1">
ff1465
-    {{{ oval_metadata("This test makes sure that " + FILEPATH + " has mode " + FILEMODE + ".
ff1465
+  {{% if FILEPATH is not string %}}
ff1465
+    {{{ oval_metadata("This test makes sure that FILEPATH has mode " + FILEMODE + ".
ff1465
+      If the target file or directory has an extended ACL, then it will fail the mode check.
ff1465
+      ") }}}
ff1465
+    <criteria>
ff1465
+  {{% for filepath in FILEPATH %}}
ff1465
+      <criterion comment="Check file mode of {{{ filepath }}}" test_ref="test_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}"{{{ ' negate="true"' if ALLOW_STRICTER_PERMISSIONS }}}/>
ff1465
+  {{% endfor %}}
ff1465
+  {{% else %}}
ff1465
+    {{{ oval_metadata("This test makes sure that " +  FILEPATH + " has mode " + FILEMODE + ".
ff1465
       If the target file or directory has an extended ACL, then it will fail the mode check.
ff1465
       ") }}}
ff1465
     <criteria>
ff1465
       <criterion comment="Check file mode of {{{ FILEPATH }}}" test_ref="test_file_permissions{{{ FILEID }}}"{{{ ' negate="true"' if ALLOW_STRICTER_PERMISSIONS }}}/>
ff1465
+  {{% endif %}}
ff1465
     </criteria>
ff1465
   </definition>
ff1465
-      <unix:file_test check="all" check_existence="{{{ FILE_EXISTENCE }}}" comment="Testing mode of {{{ FILEPATH }}}" id="test_file_permissions{{{ FILEID }}}" version="2">
ff1465
-      <unix:object object_ref="object_file_permissions{{{ FILEID }}}" />
ff1465
-      <unix:state state_ref="state_file_permissions{{{ FILEID }}}_mode_{{{ 'not_' if ALLOW_STRICTER_PERMISSIONS }}}{{{ FILEMODE }}}" />
ff1465
-    </unix:file_test>
ff1465
-    <unix:file_state id="state_file_permissions{{{ FILEID }}}_mode_{{{ 'not_' if ALLOW_STRICTER_PERMISSIONS }}}{{{ FILEMODE }}}"{{{ ' operator="OR"' if ALLOW_STRICTER_PERMISSIONS }}} version="2">
ff1465
+
ff1465
+  {{% for filepath in FILEPATH %}}
ff1465
+  <unix:file_test check="all" check_existence="{{{ FILE_EXISTENCE }}}" comment="Testing mode of {{{ filepath }}}" id="test_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}" version="2">
ff1465
+    <unix:object object_ref="object_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}" />
ff1465
+    <unix:state state_ref="state_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}_mode_{{{ 'not_' if ALLOW_STRICTER_PERMISSIONS }}}{{{ FILEMODE }}}" />
ff1465
+  </unix:file_test>
ff1465
+  <unix:file_state id="state_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}_mode_{{{ 'not_' if ALLOW_STRICTER_PERMISSIONS }}}{{{ FILEMODE }}}"{{{ ' operator="OR"' if ALLOW_STRICTER_PERMISSIONS }}} version="2">
ff1465
       {{{ STATEMODE | indent(6) }}}
ff1465
-    </unix:file_state>
ff1465
-    <unix:file_object comment="{{{ FILEPATH }}}" id="object_file_permissions{{{ FILEID }}}" version="1">
ff1465
+  </unix:file_state>
ff1465
+  <unix:file_object comment="{{{ filepath }}}" id="object_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}" version="1">
ff1465
 
ff1465
     {{%- if IS_DIRECTORY %}}
ff1465
-      <unix:path>{{{ FILEPATH }}}</unix:path>
ff1465
       {{%- if FILE_REGEX %}}
ff1465
-      <unix:filename operation="pattern match">{{{ FILE_REGEX }}}</unix:filename>
ff1465
+      <unix:path>{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename operation="pattern match">{{{ FILE_REGEX[loop.index0] }}}</unix:filename>
ff1465
+      {{%- elif RECURSIVE %}}
ff1465
+      <unix:path operation="pattern match">{{{ filepath[:-1] }}}</unix:path>
ff1465
+      <unix:filename xsi:nil="true" />
ff1465
       {{%- else %}}
ff1465
+      <unix:path>{{{ filepath[:-1] }}}</unix:path>
ff1465
       <unix:filename xsi:nil="true" />
ff1465
       {{%- endif %}}
ff1465
     {{%- else %}}
ff1465
-      <unix:filepath{{% if FILEPATH_IS_REGEX %}} operation="pattern match"{{% endif %}}>{{{ FILEPATH }}}</unix:filepath>
ff1465
+      <unix:filepath{{% if FILEPATH_IS_REGEX %}} operation="pattern match"{{% endif %}}>{{{ filepath }}}</unix:filepath>
ff1465
     {{%- endif %}}
ff1465
 
ff1465
     {{%- if ALLOW_STRICTER_PERMISSIONS %}}
ff1465
@@ -49,8 +65,8 @@
ff1465
           https://github.com/OpenSCAP/openscap/pull/1709 but this line should be kept until the
ff1465
           fix is widely available. The fix is expected to be part of OpenSCAP >= 1.3.5.
ff1465
       #}}
ff1465
-      <filter action="include">state_file_permissions{{{ FILEID }}}_mode_not_{{{ FILEMODE }}}</filter>
ff1465
+      <filter action="include">state_file_permissions{{{ FILEID }}}_{{{ loop.index0 }}}_mode_not_{{{ FILEMODE }}}</filter>
ff1465
     {{%- endif %}}
ff1465
-
ff1465
-    </unix:file_object>
ff1465
+  </unix:file_object>
ff1465
+  {{% endfor %}}
ff1465
 </def-group>
ff1465
diff --git a/shared/templates/file_permissions/template.py b/shared/templates/file_permissions/template.py
ff1465
index 677e083..6e20a62 100644
ff1465
--- a/shared/templates/file_permissions/template.py
ff1465
+++ b/shared/templates/file_permissions/template.py
ff1465
@@ -1,12 +1,25 @@
ff1465
-from ssg.utils import parse_template_boolean_value
ff1465
+from ssg.utils import parse_template_boolean_value, check_conflict_regex_directory
ff1465
 
ff1465
 def _file_owner_groupowner_permissions_regex(data):
ff1465
-    data["is_directory"] = data["filepath"].endswith("/")
ff1465
-    if "file_regex" in data and not data["is_directory"]:
ff1465
-        raise ValueError(
ff1465
-            "Used 'file_regex' key in rule '{0}' but filepath '{1}' does not "
ff1465
-            "specify a directory. Append '/' to the filepath or remove the "
ff1465
-            "'file_regex' key.".format(data["_rule_id"], data["filepath"]))
ff1465
+    # this avoids code duplicates
ff1465
+    if isinstance(data["filepath"], str):
ff1465
+        data["filepath"] = [data["filepath"]]
ff1465
+
ff1465
+    if "file_regex" in data:
ff1465
+        # we can have a list of filepaths, but only one regex
ff1465
+        # instead of declaring the same regex multiple times
ff1465
+        if isinstance(data["file_regex"], str):
ff1465
+            data["file_regex"] = [data["file_regex"]] * len(data["filepath"])
ff1465
+
ff1465
+        # if the length of filepaths and file_regex are not the same, then error.
ff1465
+        # in case we have multiple regexes for just one filepath, than we need
ff1465
+        # to declare that filepath multiple times
ff1465
+        if len(data["filepath"]) != len(data["file_regex"]):
ff1465
+            raise ValueError(
ff1465
+                "You should have one file_path per file_regex. Please check "
ff1465
+                "rule '{0}'".format(data["_rule_id"]))
ff1465
+
ff1465
+    check_conflict_regex_directory(data)
ff1465
 
ff1465
 
ff1465
 def preprocess(data, lang):
ff1465
@@ -16,6 +29,10 @@ def preprocess(data, lang):
ff1465
 
ff1465
     data["missing_file_pass"] = parse_template_boolean_value(data, parameter="missing_file_pass", default_value=False)
ff1465
 
ff1465
+    data["recursive"] = parse_template_boolean_value(data,
ff1465
+                                                     parameter="recursive",
ff1465
+                                                     default_value=False)
ff1465
+
ff1465
     if lang == "oval":
ff1465
         data["fileid"] = data["_rule_id"].replace("file_permissions", "")
ff1465
         # build the state that describes our mode
ff1465
diff --git a/ssg/utils.py b/ssg/utils.py
ff1465
index b0ded09..2248b1e 100644
ff1465
--- a/ssg/utils.py
ff1465
+++ b/ssg/utils.py
ff1465
@@ -303,3 +303,25 @@ def parse_template_boolean_value(data, parameter, default_value):
ff1465
         raise ValueError(
ff1465
             "Template parameter {} used in rule {} cannot accept the "
ff1465
             "value {}".format(parameter, data["_rule_id"], value))
ff1465
+
ff1465
+
ff1465
+def check_conflict_regex_directory(data):
ff1465
+    """
ff1465
+    Validate that either all path are directories OR file_regex exists.
ff1465
+
ff1465
+    Throws ValueError.
ff1465
+    """
ff1465
+    for f in data["filepath"]:
ff1465
+        if "is_directory" in data and data["is_directory"] != f.endswith("/"):
ff1465
+            raise ValueError(
ff1465
+                "If passing a list of filepaths, all of them need to be "
ff1465
+                "either directories or files. Mixing is not possible. "
ff1465
+                "Please fix rules '{0}' filepath '{1}'".format(data["_rule_id"], f))
ff1465
+
ff1465
+        data["is_directory"] = f.endswith("/")
ff1465
+
ff1465
+        if "file_regex" in data and not data["is_directory"]:
ff1465
+            raise ValueError(
ff1465
+                "Used 'file_regex' key in rule '{0}' but filepath '{1}' does not "
ff1465
+                "specify a directory. Append '/' to the filepath or remove the "
ff1465
+                "'file_regex' key.".format(data["_rule_id"], f))