Skip to content

Commit

Permalink
Merge pull request #10838 from jan-cerny/rhbz2213958
Browse files Browse the repository at this point in the history
Change rules related to /etc/shadow to check only local user configuration
  • Loading branch information
vojtapolasek committed Jul 18, 2023
2 parents 9dfe74e + e7424a0 commit 7cab0f8
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 212 deletions.
Original file line number Diff line number Diff line change
@@ -1,59 +1,5 @@
<def-group>
<definition class="compliance" id="{{{ rule_id }}}" version="2">
{{{ oval_metadata("Set Existing Passwords Maximum Age") }}}
<criteria operator="AND">
<criterion test_ref="test_password_max_life_existing"
comment="Passwords must be restricted to the appropriate maximum age for existing accounts."/>
<criterion test_ref="test_password_max_life_existing_minimum"
comment="Passwords must have a maximum lifetime greater than or equal minimum password age."/>
</criteria>
</definition>

<!-- Define a test for the shadow file for non-system accounts to look for the maximum
password change interval. -->
<unix:shadow_test id="test_password_max_life_existing" version="1"
check="all" check_existence="any_exist"
comment="Password maximum lifetime for existing accounts is at least the minimum.">
<unix:object object_ref="object_shadow_password_users_max_life_existing"/>
<unix:state state_ref="max_password_change_interval"/>
</unix:shadow_test>

<!-- Define a second test to ensure the maximum password life is at least the defined
minimum (usually 1). -->
<unix:shadow_test id="test_password_max_life_existing_minimum" version="1"
check="all" check_existence="any_exist"
comment="Password maximum life entry is at least a defined minimum">
<unix:object object_ref="object_shadow_password_users_max_life_existing"/>
<unix:state state_ref="min_max_password_change_interval"/>
</unix:shadow_test>

<unix:shadow_object id="object_shadow_password_users_max_life_existing" version="1">
<unix:username operation="pattern match">.*</unix:username>
<filter action="include">filter_no_passwords_or_locked_accounts_max_life</filter>
</unix:shadow_object>

<unix:shadow_state id="filter_no_passwords_or_locked_accounts_max_life" version="1">
<unix:password operation="pattern match">^[^\!\*]*$</unix:password>
</unix:shadow_state>

<unix:shadow_state id="min_max_password_change_interval" version="1"
comment="change passwords every minimum interval or more">
<unix:password operation="pattern match" mask="true">.*</unix:password>
<unix:chg_req operation="greater than or equal" datatype="int"
var_ref="var_accounts_minimum_age_login_defs"/>
</unix:shadow_state>

<unix:shadow_state id="max_password_change_interval" version="1"
comment="change passwords at the recommended interval or less">
<unix:password operation="pattern match" mask="true">.*</unix:password>
<unix:chg_req operation="less than or equal" datatype="int"
var_ref="var_accounts_maximum_age_login_defs"/>
</unix:shadow_state>

<!-- these external variables are defined at the group level,
reusing the account-level definitions. -->
<external_variable id="var_accounts_maximum_age_login_defs" datatype="int" version="1"
comment="Maximum password age"/>
<external_variable id="var_accounts_minimum_age_login_defs" datatype="int" version="1"
comment="Minimum password age"/>
</def-group>
{{# The regular expression filters away accounts with no passwords and locked passwords (passwords are located in the 2nd field of /etc/shadow entries). #}}
{{{ etc_shadow_test(
regex='^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){2}(\d+):(?:[^:]*:){3}(?:[^:]*)$',
regex_empty='^(?:[^:]*:)(?:[^\!\*:]+:)(?:[^:]*:){2}():(?:[^:]*:){3}(?:[^:]*)$'
) }}}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
documentation_complete: true

prodtype: alinux3,anolis23,anolis8,ol7,ol8,ol9,rhel7,rhel8,rhel9,rhv4,sle12,sle15,ubuntu2004,ubuntu2204
prodtype: alinux3,anolis23,anolis8,fedora,ol7,ol8,ol9,rhel7,rhel8,rhel9,rhv4,sle12,sle15,ubuntu2004,ubuntu2204

title: 'Set Existing Passwords Maximum Age'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,5 @@
<def-group>
<definition class="compliance" id="accounts_password_set_min_life_existing" version="1">
{{{ oval_metadata("Passwords for existing accounts much satisfy minimum age requirements") }}}
<criteria operator="AND">
<criterion comment="Passwords must be restricted to the appropriate minimum age for existing accounts." test_ref="test_password_min_life_existing" />
<criterion comment="Passwords must have a minimum lifetime less than or equal to the defined maximum." test_ref="test_password_min_life_existing_maximum" />
</criteria>
</definition>

<!-- Define a test for the shadow file for accounts with passwords to look for the minimum password change interval. -->
<unix:shadow_test
id="test_password_min_life_existing"
check="all"
check_existence="any_exist"
version="1"
comment="Password minimum lifetime for existing accounts is at least what is defined by policy.">
<unix:object object_ref="object_shadow_password_users_min_life_existing"/>
<unix:state state_ref="min_password_change_interval"/>
</unix:shadow_test>

<!-- Define a second test to ensure the minimum password life is at less than the defined maximum. -->
<unix:shadow_test id="test_password_min_life_existing_maximum" check="all" check_existence="any_exist" version="1" comment="Password minimum life entry is at mosta defined maximum">
<unix:object object_ref="object_shadow_password_users_min_life_existing"/>
<unix:state state_ref="max_min_password_change_interval"/>
</unix:shadow_test>

<unix:shadow_object id="object_shadow_password_users_min_life_existing" version="1">
<unix:username operation="pattern match">.*</unix:username>
<filter action="include">filter_no_passwords_or_locked_accounts_min_life</filter>
</unix:shadow_object>

<unix:shadow_state id="filter_no_passwords_or_locked_accounts_min_life" version="1">
<unix:password operation="pattern match">^[^\!\*]*$</unix:password>
</unix:shadow_state>

<unix:shadow_state id="max_min_password_change_interval" version="1" comment="change passwords every maximum interval or less">
<unix:password operation="pattern match" mask="true">.*</unix:password>
<unix:chg_allow
operation="less than or equal"
datatype="int"
var_ref="var_accounts_maximum_age_login_defs"/>
</unix:shadow_state>

<unix:shadow_state id="min_password_change_interval" version="1" comment="change passwords at at the recommended interval or more">
<unix:password operation="pattern match" mask="true">.*</unix:password>
<unix:chg_allow
operation="greater than or equal"
datatype="int"
var_ref="var_accounts_minimum_age_login_defs"/>
</unix:shadow_state>

<!-- these external variables are defined at the group level, reusing the account-level definitions. -->
<external_variable
comment="Maximum password age"
datatype="int"
id="var_accounts_maximum_age_login_defs"
version="1" />

<external_variable
comment="Minimum password age"
datatype="int"
id="var_accounts_minimum_age_login_defs"
version="1" />


</def-group>
{{# The regular expression filters away accounts with no passwords and locked passwords (passwords are located in the 2nd field of /etc/shadow entries). #}}
{{{ etc_shadow_test(
regex='^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:)(\d+):(?:[^:]*:){4}(?:[^:]*)$',
regex_empty='^(?:[^:]*:)(?:[^\!\*:]+:)(?:[^:]*:)():(?:[^:]*:){4}(?:[^:]*)$'
) }}}
Original file line number Diff line number Diff line change
@@ -1,44 +1,6 @@
<def-group>
<definition class="compliance" id="{{{ rule_id }}}" version="1">
{{{ oval_metadata("Set Existing Passwords Warning Age") }}}
<criteria operator="OR">
<criterion test_ref="test_accounts_password_set_warn_age_existing"
comment="Passwords must be configured to the appropriate warn age before expiring."/>
<criterion test_ref="test_accounts_password_set_warn_age_existing_no_pass"
comment="There is no password defined in /etc/shadow"/>
</criteria>
</definition>

<unix:shadow_test id="test_accounts_password_set_warn_age_existing" version="1"
check="all" check_existence="at_least_one_exists"
comment="Password expiration warn age for existing accounts is properly set in /etc/shadow.">
<unix:object object_ref="object_accounts_password_set_warn_age_existing"/>
<unix:state state_ref="state_warn_age_for_passwords_change"/>
</unix:shadow_test>

<unix:shadow_object id="object_accounts_password_set_warn_age_existing" version="1">
<unix:username operation="pattern match">.*</unix:username>
<filter action="exclude">state_accounts_password_set_warn_age_existing_no_password</filter>
</unix:shadow_object>

<unix:shadow_state id="state_accounts_password_set_warn_age_existing_no_password" version="1">
<unix:password operation="pattern match">^(!|!!|!\*|\*|!locked)$</unix:password>
</unix:shadow_state>

<unix:shadow_state id="state_warn_age_for_passwords_change" version="1"
comment="Warn age for passwords expiration is set to the recommended value.">
<unix:exp_warn operation="greater than or equal" datatype="int"
var_ref="var_accounts_password_warn_age_login_defs"/>
</unix:shadow_state>

<!-- this external variable is defined at the group level,
reusing the account-level definitions. -->
<external_variable id="var_accounts_password_warn_age_login_defs" version="1"
datatype="int" comment="Warning days before password expires"/>

<unix:shadow_test id="test_accounts_password_set_warn_age_existing_no_pass" version="1"
check="all" check_existence="none_exist"
comment="Check the inexistence of users with a password defined">
<unix:object object_ref="object_accounts_password_set_warn_age_existing"/>
</unix:shadow_test>
</def-group>
{{# The regular expression filters away accounts with no passwords and locked passwords (passwords are located in the 2nd field of /etc/shadow entries). #}}
{{{ test_etc_shadow_password_variable(
regex="^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){3}(\d+):(?:[^:]*:){2}(?:[^:]*)$",
external_variable_id="var_accounts_password_warn_age_login_defs",
operation="greater than or equal",
description="Set Existing Passwords Warning Age") }}}
Original file line number Diff line number Diff line change
@@ -1,42 +1,6 @@
<def-group>
<definition class="compliance" id="{{{ rule_id }}}" version="1">
{{{ oval_metadata("Set existing passwords a period of inactivity before they been locked") }}}
<criteria operator="OR">
<criterion test_ref="test_accounts_set_post_pw_existing"
comment="Passwords must be configured to the appropriate period of inactivity."/>
<criterion test_ref="test_accounts_set_post_pw_existing_no_pass"
comment="There is no password defined in /etc/shadow"/>
</criteria>
</definition>

<unix:shadow_test id="test_accounts_set_post_pw_existing" version="1"
check="all" check_existence="at_least_one_exists"
comment="Password INACTIVE parameter is no more than 30 days.">
<unix:object object_ref="object_shadow_password_users_post_pw_existing"/>
<unix:state state_ref="inactive_param_for_passwords_change"/>
</unix:shadow_test>

<unix:shadow_object id="object_shadow_password_users_post_pw_existing" version="1">
<unix:username operation="pattern match">.*</unix:username>
<filter action="exclude">state_accounts_set_post_pw_existing_no_password</filter>
</unix:shadow_object>

<unix:shadow_state id="state_accounts_set_post_pw_existing_no_password" version="1">
<unix:password operation="pattern match">^(!|!!|!\*|\*|!locked)$</unix:password>
</unix:shadow_state>

<unix:shadow_state id="inactive_param_for_passwords_change" version="1"
comment="change INACTIVE parameter for passwords to the recommended value">
<unix:exp_inact operation="less than or equal" datatype="int"
var_ref="var_account_disable_post_pw_expiration"/>
</unix:shadow_state>

<external_variable id="var_account_disable_post_pw_expiration" datatype="int" version="1"
comment="Number of days after an inactive user account can be automatically disabled"/>

<unix:shadow_test id="test_accounts_set_post_pw_existing_no_pass" version="1"
check="all" check_existence="none_exist"
comment="Check the inexistence of users with a password defined">
<unix:object object_ref="object_shadow_password_users_post_pw_existing"/>
</unix:shadow_test>
</def-group>
{{# The regular expression filters away accounts with no passwords and locked passwords (passwords are located in the 2nd field of /etc/shadow entries). #}}
{{{ test_etc_shadow_password_variable(
regex="^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){4}(\d+):(?:[^:]*:)(?:[^:]*)$",
external_variable_id="var_account_disable_post_pw_expiration",
operation="less than or equal",
description="Set existing passwords a period of inactivity before they been locked") }}}
136 changes: 136 additions & 0 deletions shared/macros/10-oval.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -1291,3 +1291,139 @@ Generates the :code:`<affected>` tag for OVAL check using correct product platfo
</ind:textfilecontent54_object>
{{%- endmacro %}}

{{#
Generates an OVAL check that checks a particular field in the "/etc/shadow" file.

:param regex: Regex that matches the entries in shadow file and captures the particular field value to a subexpression
:type regex: str
:param regex_empty: Regex that matches the entries in shadow file if the particular field value is empty
:type regex_empty: str

#}}
{{%- macro etc_shadow_test(regex, regex_empty) %}}
<def-group>
<definition class="compliance" id="{{{ rule_id }}}" version="3">
{{{ oval_metadata("Set Existing Passwords Maximum Age") }}}
<criteria operator="AND">
<criterion test_ref="test_{{{ rule_id }}}_password_max_life_existing"
comment="Passwords must be restricted to the appropriate maximum age for existing accounts."/>
<criterion test_ref="test_{{{ rule_id }}}_password_max_life_existing_minimum"
comment="Passwords must have a maximum lifetime greater than or equal minimum password age."/>
<criterion test_ref="test_{{{ rule_id }}}_password_max_life_not_empty"
comment="Passwords must have the maximum password age set non-empty in /etc/shadow."/>
</criteria>
</definition>

<!-- Define a test for the /etc/shadow file for non-system accounts to look for the maximum
password change interval. -->
{{{ textfilecontent54_test_etc_shadow(
test_id="test_" + rule_id + "_password_max_life_existing",
regex=regex,
external_variable_id="var_accounts_maximum_age_login_defs",
operation="less than or equal") }}}

<!-- Define a second test to ensure the maximum password life is at least the defined
minimum (usually 1). -->
{{{ textfilecontent54_test_etc_shadow(
test_id="test_" + rule_id + "_password_max_life_existing_minimum",
regex=regex,
external_variable_id="var_accounts_minimum_age_login_defs",
operation="greater than or equal") }}}

<ind:textfilecontent54_test id="test_{{{ rule_id }}}_password_max_life_not_empty" version="1"
check="all" check_existence="none_exist"
comment="Passwords must have the maximum password age set non-empty in /etc/shadow.">
<ind:object object_ref="object_{{{ rule_id }}}_shadow_password_users_max_life_not_existing"/>
</ind:textfilecontent54_test>

<ind:textfilecontent54_object id="object_{{{ rule_id }}}_shadow_password_users_max_life_not_existing" version="1">
<ind:filepath>/etc/shadow</ind:filepath>
<ind:pattern operation="pattern match">{{{ regex_empty }}}</ind:pattern>
<ind:instance operation="greater than or equal" datatype="int">1</ind:instance>
</ind:textfilecontent54_object>

</def-group>
{{%- endmacro %}}

{{#
Generates an OVAL test used in checks a particular field in the "/etc/shadow" file.

:param test_id: OVAL test ID
:type test_id: str
:param regex: Regex that matches the entries in shadow file and captures the particular field value to a subexpression
:type regex: str
:param external_variable_id: External variable ID
:type external_variable_id: str
:param operation: operation
:type operation: str

#}}
{{%- macro textfilecontent54_test_etc_shadow(test_id, regex, external_variable_id, operation) -%}}

<ind:textfilecontent54_test id="{{{ test_id }}}" version="1"
check="all" check_existence="any_exist"
comment="Compares a specific field in /etc/shadow with a specific variable value">
<ind:object object_ref="object_{{{ test_id }}}"/>
<ind:state state_ref="state_{{{ test_id }}}"/>
</ind:textfilecontent54_test>

<ind:textfilecontent54_object id="object_{{{ test_id }}}" version="1">
<ind:filepath>/etc/shadow</ind:filepath>
<ind:pattern operation="pattern match">{{{ regex }}}</ind:pattern>
<ind:instance operation="greater than or equal" datatype="int">1</ind:instance>
</ind:textfilecontent54_object>

<ind:textfilecontent54_state id="state_{{{ test_id }}}" version="1">
<ind:subexpression datatype="int" operation="{{{ operation }}}" var_check="all"
var_ref="{{{ external_variable_id }}}"/>
</ind:textfilecontent54_state>

<external_variable id="{{{ external_variable_id }}}" datatype="int" version="1"
comment="External variable"/>
{{%- endmacro %}}

{{#
Generates an OVAL check that checks a particular field in the "/etc/shadow" file and compares it with a given XCCDF Value variable.

:param regex: Regex that matches the entries in shadow file and captures the particular field value to a subexpression
:type regex: str
:param external_variable_id: External variable ID
:type external_variable_id: str
:param operation: operation
:type operation: str
:param description: brief description for OVAL metadata
:type description: str

#}}
{{%- macro test_etc_shadow_password_variable(regex, external_variable_id, operation, description) -%}}
<def-group>
<definition class="compliance" id="{{{ rule_id }}}" version="1">
{{{ oval_metadata(description) }}}
<criteria operator="OR">
<criterion test_ref="test_{{{ rule_id }}}"
comment="Passwords must be configured to the appropriate value"/>
<criterion test_ref="test_{{{ rule_id }}}_no_pass"
comment="There is no password defined in /etc/shadow"/>
</criteria>
</definition>

{{{ textfilecontent54_test_etc_shadow(
test_id="test_" + rule_id,
regex=regex,
external_variable_id=external_variable_id,
operation=operation) }}}

<ind:textfilecontent54_test id="test_{{{ rule_id }}}_no_pass" version="1"
check="all" check_existence="none_exist"
comment="Check the inexistence of users with a password defined">
<ind:object object_ref="object_{{{ rule_id }}}_no_pass"/>
</ind:textfilecontent54_test>

<ind:textfilecontent54_object id="object_{{{ rule_id }}}_no_pass" version="1">
<ind:filepath>/etc/shadow</ind:filepath>
<ind:pattern operation="pattern match">{{{ regex }}}</ind:pattern>
<ind:instance operation="greater than or equal" datatype="int">1</ind:instance>
</ind:textfilecontent54_object>

</def-group>
{{%- endmacro %}}

0 comments on commit 7cab0f8

Please sign in to comment.