ansible-lockdown / RHEL7-STIG

Ansible role for Red Hat 7 STIG Baseline
MIT License
284 stars 144 forks source link

Playbook needs password set or verified for user running the playbook #414

Closed bordenit closed 1 year ago

bordenit commented 2 years ago

Describe the Issue The playbook needs password set for the user that is running the pipeline and ansible_become_password needs to be set in the playbook vars.

Recommended approach Make this a fail safe for the user experience as well as the pipeline. The password needs to be verified early on in the playbook OR the password needs to be set before the password requirement is implemented in the playbook to ensure user is not locked out of system.

TASK [/github/workspace : MEDIUM | RHEL-07-010344 | PATCH | The Red Hat Enterprise Linux operating system must not be configured to bypass password requirements for privilege escalation.] ***
fatal: [centos7]: FAILED! => {"msg": "Missing sudo password"}

Expected Behavior Password is verified in earlier tasks, or is set, in the playbook prior to this task failing. Task and follow on tasks complete.

Actual Behavior Task fails. User is potentially locked out of system. User needs to restore system.

bordenit commented 2 years ago

Something like this may get 1/3 of the way there, but still does not verify the password. Not sure there is a safe way to pass in a password to be verified in Ansible, but at least this will fail if you don't have one at all...

- name: PREREQ | Fail hard if current user does not have a password
  become: true
  shell: |
    #!/bin/bash
    has_pass=$(getent shadow | grep $(whoami) | cut -d ":" -f 2)
    if [ $has_pass = "!!" ]; then
      echo "true"
    else
      echo "false"
    fi
  register: ansible_user_has_password
  failed_when: ansible_user_has_password.stdout | bool == false
uk-bolly commented 2 years ago

hi @bordenit

Thank you for raising this issue. The control causing this is RHEL-08-010340. I have now added this at two levels. README has it listed individually rather than under a generic statement. I have also made the control 010340 added to disruption_is_high variable, so this needs to be specifically set. Very best

uk-bolly

whitehat237 commented 1 year ago

In our case, we're using freeipa to authenticate to the system. Therefore the user account is not defined in /etc/passwd, and doesn't have an entry in /etc/shadow. sss lookup is used as the identity provider, not files. We have the var "use_sssd:" set to "true". Perhaps this hard fail can be skipped when use_sssd is true and the user account can be verified by sss lookup, even when it doesn't exist in /etc/passwd, or /etc/shadow? (Not a local account)

uk-bolly commented 1 year ago

hi @whitehat237

Thank you for the feedback, these side cases are rarely covered by any of the documentation and its great to get feedback like this. I am sure there are other controls that an externally managed authentication system wouldn't require other items to be adjusted also e.g. cyberark Would it be worth adding a new variable for external user management and then working through each control. This allows to be alot more specific and could provide more options when and when not to use? We would appreciate any feedback on this and we would require testing also.

best regards

uk-bolly

whitehat237 commented 1 year ago

@uk-bolly Yes, I think it's an interesting idea, and would be worthwhile.

For this particular task in prereq.yml, a variable could be used to specify if the user account being used to run the role, is an externally authenticated user or not. More specifically, is the password attribute for the account provided locally or is it sourced from a remote provider? (Since the control seems to be primarily concerned with whether or not the user invoking the role has an established password attribute set. In this case by looking in /etc/shadow)

Perhaps one of the following variable names would work?

rhel7stig_role_applying_user_remotely_authenticated: false

rhel7stig_role_applying_user_remote_pw_attribute: false

With the default role shipped value set as false.

When set to true, the task should be skipped:


- name: PREREQ | Fail hard if current user does not have a password
  become: true
  shell: |
    #!/bin/bash
    has_pass=$(getent shadow | grep $(whoami) | cut -d ":" -f 2)
    if [ $has_pass = "!!" ]; then
      echo "true"
    else
      echo "false"
    fi
  register: ansible_user_has_password
  failed_when: ansible_user_has_password.stdout | bool == false
  when: not rhel7stig_role_applying_user_remotely_authenticated

Regarding your idea of having a variable for external user management.

I think it's sensible to determine if the user account is defined in /etc/passwd, and /etc/shadow, similar to what the task is already doing now in prereq, and use this same logic as the method to determine if the account entry is or is not defined locally and if it isn't, exclude it from the list of discovered local users used later on by other role tasks.

If a local account is present, all of the usual role controls should still be applied for that account, and setting the variable in question to true should not disable the implementation of applicable controls for real local accounts.

In our use case there is a single local account present (defined in /etc/passwd and /etc/shadow), but this account is not used to "apply" the RHEL7-STIG role, and it's not needed solely for the purpose of running the role.

We also have a couple of organizational security policy requirement that affect this:

  1. All "local" accounts have to be documented and approved by the org security team.
  2. Local accounts are not allowed to be present in the wheel group. (The local account is not allowed to elevate via some method like sudo or ksu, but can still use su with root password.)

One can install RHEL 7 and have no local account present. (Excluding root) by just not creating the local account in the installer / kickstart file in order to be compliant with these controls by default. In that case, user authentication requests to the system may be handled by an external identity provider such as IPA, ldap, AD, etc.

One question might be, how should the role reliably determine if an external identity provider is connected and used? sssd seems to be the standard lookup provider on most (all?) enterprise Linux 7 systems.

Based on that assumption, we first considered looking at /etc/nsswitch.conf to see if sss is specified, however it does not seem to be sufficient to just look at whether or not sss is used in nsswitch, because on a default install of RHEL 7 with no connected identity provider, it appears that sss is still present in the passwd line in nsswitch.conf (based on our testing)

One idea we have, and method we're planning to test is to recursively check if an identity provider is configured in /etc/sssd/* (Due to it being possible to have /etc/sssd.d/50-ad.conf or a similar numbered config snippet, where a provider could be configured)

grep -i "id_provider" /etc/sssd* -R

The idea here is that if an id_provider is configured, this could be registered in a task, and used to set a corresponding ansible variable like use_sssd to true, and be subsequently used by other role tasks to implement a control or not.

Another task in prereq.yml could then perform lookup on discovered users when an external identity provider is in use, to validate that the account is actually defined in /etc/passwd and /etc/shadow before producing the final list of discovered users.

Thoughts?

uk-bolly commented 1 year ago

hi @whitehat237

Thank you for your very informative feedback. I have taken a few of your thoughts and added a check in the assertion again. This time to see if this is a local account. It will skip if either

rhel7stig_role_applying_user_remotely_authenticated is set to true or the getent lookup not found in the /etc/shadow file. I am sure there are more ways to work through this and may come back to looking at nsswitch as well or using the fact ansible_domain but as that uses the socket.getfqdn() python module which in itself appears to have a number of issues.

Any more thoughts or testing you can provide would be great, always good to get a different view on how things work. I have created branch issue_#414 if you get a chance to test.

Many thanks again

uk-bolly

whitehat237 commented 1 year ago

@uk-bolly I will try to test branch 414 today.

Thanks for submitting this.

whitehat237 commented 1 year ago

@uk-bolly I finally had a chance to test branch 414.

I implemented the changes from your commits on my local RHEL7-STIG role and ran the role against a RHEL 7.9 physical system.

It works as intended.

I ran it twice, the first run had rhel7stig_role_applying_user_remotely_authenticated set as true The second run through had it set to false.

On the second run through, your changes worked properly and the assertion task to ensure that a password is set for {{ ansible_user }} if local account showed as skipped.

In both runs I used an external IDM authenticated account to run the role.

whitehat237 commented 1 year ago

@uk-bolly As a side note, I also tested these changes with RHEL8-STIG on a RHEL 8.6 system as they relate to the task for RHEL-08-010380 in tasks/main.yml

The changes work for RHEL8-STIG as well, without issue.

uk-bolly commented 1 year ago

all merged and sorted thanks for your feedback.