Skip to content

Remediating RHEL-09-431016

I get a lot of questions about how to remediate RHEL-09-431016. People report issues like sudo or SSH no longer working afterwards. I was discussing this with my partner in crime, and we ultimately came to the conclusion that unless you really know the RHEL product or you were intimately familiar with the RHEL 7 STIG you would never know that there are a couple of missing links in the process for making RHEL-09-431016 work properly. We had to learn these things the hard way by watching test systems brick over the years, so keep in mind these are lessons we learned back with RHEL 7 and carried forward because not only would we have consistent baselines between generations, but we genuinely believed that the STIG would eventually catch up because these controls are necessary in the context of RHEL-09-431016. You'll see some of that reflected in the Ansible task naming included in this post where we carried forward two critical controls that enable RHEL-09-431016 to function without bricking the system.

As a bonus, I'm also sharing some of our SELINUX policy modules. These might not be necessary now, but they were at the time that we built our compliance automation products.

Related pre-reading: Compiling selinux policy packages

First, we are going to need to generate a series of selinux modules to distribute to our hosts. We "pre-bake" these and include the files in our code repository. Each of these items represents something we noticed was 'broken' or generating noise in our logs.

  • sudo_ssh.te - compile this into sudo_ssh.pp
module sudo_ssh 1.0;

require {
    type user_tmp_t;
    type staff_sudo_t;
    class sock_file getattr;
        type init_t;
        type staff_t;
        class process getpgid;
        class unix_stream_socket connectto;
        class sock_file write;
}

#============= staff_sudo_t ==============
allow staff_sudo_t init_t:process getpgid; 
allow staff_sudo_t staff_t:unix_stream_socket connectto;
allow staff_sudo_t user_tmp_t:sock_file { getattr write };
  • site-local_vlock.te - compile this into site-local_vlock.pp
module site-local_vlock 1.1;

require {
        type vlock_t;
        type devpts_t;
        class dir getattr;
        class dir search;
}

# This policy allows vlock to run for confined users

#============= vlock_t ==============

#!!!! This avc is allowed in the current policy
allow vlock_t devpts_t:dir getattr;
allow vlock_t devpts_t:dir search;
  • Some stuff we needed for rootless containers to work properly - compile this into rootless_container.pp
module rootless_container 1.5;

require {
    type proc_t;
    type cert_t;
    type user_home_dir_t;
    type user_t;
    type container_t;
    type container_runtime_t;
    class file { ioctl open read getattr write create };
    class dir { search write add_name };
    class filesystem associate;
    class process signull;
}

#============= container_t ==============
allow container_t cert_t:file { ioctl open read getattr };
allow container_t proc_t:filesystem associate;
allow container_t user_home_dir_t:file read;
allow container_t self:dir { add_name write };
allow container_t self:file { create };

Once you have those files compiled and staged with your project, you can add some Ansible tasks like the ones below. Keep in mind that we use Ansible Automation Platform and centralize all of our stuff. You may need to adjust the syntax here to account for site differences. Also, incidents of "site-local" are where I have scrubbed the customer's site name. We typically wrap our playbook execution with tasks for selinux permissive and enforcing, which I have included around this block of tasks for your convenience.

Again, the selinux policy modules are for things we noticed were still broken after logging in seemed to work. The control tasks inherited from RHEL-07-020020 and RHEL-07-020021 are basically the missing pieces to your puzzle. Without these role assignments, people will have 'no permissions' when they log in. Specifically, staff_u needs the staff_r and sysadm_r roles assigned. You need a role to rock and roll! Also, we have an account besides root that we use as our last resort SSH user. You will see that account referenced by site-local-last-resort-user in the example. Change that to mycooladmin or whatever you guys use at your site.

- name: SELinux permissive
    ansible.posix.selinux:
    policy: targeted
    state: permissive
    tags: always

- name: SELinux configs
  tags:
    - selinux
  block:
    - name: List SELinux modules
      ansible.builtin.command: semodule -lfull
      register: selinux_loaded_modules
      changed_when: false

    - name: RHEL-09-SITE-LOCALFIX Copy site-local policy module for staff_sudo_t to read the ssh agent socket
      ansible.builtin.copy:
        src: files/selinux/sudo_ssh.pp
        dest: /root/sudo_ssh.pp
        owner: root
        group: root
        mode: "0600"
      register: selinux_module_sudo_ssh

    - name: RHEL-09-SITE-LOCALFIX activate site-local policy module for staff_sudo_t to read the ssh agent socket
      ansible.builtin.command: semodule -i /root/sudo_ssh.pp
      changed_when: true
      when: (selinux_module_sudo_ssh.changed) or ('sudo_ssh' not in selinux_loaded_modules.stdout)

    - name: RHEL-09-SITE-LOCALFIX Copy site-local policy module for site-local_vlock
      ansible.builtin.copy:
        src: files/selinux/site-local_vlock.pp
        dest: /root/site-local_vlock.pp
        owner: root
        group: root
        mode: "0600"
      register: selinux_module_site-local_vlock

    - name: RHEL-09-SITE-LOCALFIX activate site-local policy module for site-local_vlock
      ansible.builtin.command: semodule -i /root/site-local_vlock.pp
      changed_when: true
      when: (selinux_module_site-local_vlock.changed) or ('site-local_vlock' not in selinux_loaded_modules.stdout)

    - name: RHEL-09-SITE-LOCALFIX Copy site-local policy module for rootless_container
      ansible.builtin.copy:
        src: files/selinux/rootless_container.pp
        dest: /root/rootless_container.pp
        owner: root
        group: root
        mode: "0600"
      register: selinux_module_rootless_container

    - name: RHEL-09-SITE-LOCALFIX activate site-local policy module for rootless_container
      ansible.builtin.command: semodule -i /root/rootless_container.pp
      changed_when: true
      when: (selinux_module_rootless_container.changed) or ('rootless_container' not in selinux_loaded_modules.stdout)

# This next task was originally a block with some additional logic to make it so the task only engaged if the 
# users didn't already have the roles assigned. I'll let the original author of that wizardry share his solution 
# if he's feeling generous, but I took it out. It was slick, but hard to follow if you're just a normal 
# human being like the rest of us. 
    - name: RHEL-09-WEKNOWITSCOMING - inherited from RHEL-07-020021
      ansible.builtin.command: semanage user -m {{ item.user }} {{ ['-R '] | product(item.roles) | map('join') | join(' ') }}
      changed_when: true
      loop_control:
      label: "{{ item.user }}"
      with_items:
        # Example
        # - user: <selinux user>
        #   roles:
        #     - <list of roles>
        - user: user_u
            roles:
            - user_r
        - user: staff_u
            roles:
            - staff_r
            - sysadm_r
      tags:
        - RHEL-09-WEKNOWITSCOMING
        - RHEL-07-020021

    - name: RHEL-09-WEKNOWITSCOMING user login mappings - inherited from RHEL-07-020020
      community.general.selogin:
        login: "{{ item.user }}"
        seuser: "{{ item.seuser }}"
        selevel: "{{ item.selevel }}"
        state: present
      tags:
        - RHEL-09-WEKNOWITSCOMING
        - RHEL-07-020020
      with_items:
        # Example
        # - user: <username>
        #   seuser: <selinux user>
        #   selevel: <mls level>
        - user: site-local-last-resort-user
          seuser: staff_u
          selevel: s0-s0:c0.c1023
        - user: __default__
          seuser: user_u
          selevel: s0
      loop_control:
        label: "{{ item.user }}"

    - name: Reset SSH connection to refresh selinux roles, groups, stuff, etc.
      ansible.builtin.meta: reset_connection

    - name: RHEL-09-431016 Clean up old file from RHEL-07-020023 if it is still present
      ansible.builtin.file:
        path: /etc/sudoers.d/RHEL-07-020023
        state: absent
      tags:
        - RHEL-09-431016

    - name: RHEL-09-431016 apply sysadm_t and sysadm_r in /etc/sudoers.d/RHEL-09-431016
      ansible.builtin.lineinfile:
        path: /etc/sudoers.d/RHEL-09-431016
        line: "%wheel ALL=(ALL) TYPE=sysadm_t ROLE=sysadm_r ALL"
        create: true
        mode: "0600"
        owner: root
        group: root
      tags:
        - RHEL-09-431016

  always:
    - name: SELinux enforcing
        ansible.posix.selinux:
        policy: targeted
        state: enforcing
        tags: always        

That should get you compliant AND functional. It's been working for us when applied to fleets of RHEL across 3 networks. Good luck!