September 2, 2017 · ansible playbook sssd centos ad active directory

Ansible playbook: Join CentOS server to Active Directory

As your homelab or enterprise expands with new servers it tends to get more and more frustrating to keep track of all local user accounts and passwords. One simple way to minimize the frustration is to utilize something that, I dare say, every organization already uses.

Microsoft Active Directory.

This playbook will join your CentOS server to the Active Directory and limit logon access and sudo access using security groups.

Keep in mind that the playbook in this post has been used to bulk join multiple servers to the AD. Feel free to customize it to fit your needs.

Go ahead and skim through the playbook. I will go in to detail further down in the post.

---
## This playbook installs and configures AD authentication

- name: Install and configure AD authentication
  hosts: centos  
  become: yes
  become_method: sudo
  vars_prompt:
    - name: "bind_password"
      prompt: "Password for safdal\\administrator"
      private: yes

  tasks:
    - name: Install ad_auth required tools
      yum: 
        name: libselinux-python,realmd,sssd,oddjob,oddjob-mkhomedir,adcli,samba-common,samba-common-tools,ntpdate,ntp,python-pip,sudo
        state: present

    - name: Install pexpect using pip
      pip:
        name: pexpect

    - name: Check if machine is bound
      shell: /bin/bash -c "realm list | grep sssd"
      register: realmd_bound
      changed_when: false
      ignore_errors: true

    - name: Join system to AD and put the computer object in the Linux OU
      expect:
        command: /bin/bash -c "/usr/sbin/realm join --user=ADMINISTRATOR@safdal.se --computer-ou=OU=Linux,OU=Servers,OU=Safdal,DC=safdal,DC=se safdal.se"
        responses:
          Password for *: "{{ bind_password }}"
      when: realmd_bound|failed

    - name: Add default_domain_suffix to sssd.conf
      lineinfile: 
        dest: /etc/sssd/sssd.conf
        line: 'default_domain_suffix = safdal.se'
        insertafter: '^\[sssd\]'
      notify:
        - restart sssd
      when: realmd_bound|failed

    - name: Restrict access based on specific ad group
      command: /bin/bash -c "/usr/sbin/realm permit -g g_Server.{{ item }}-logon@safdal.se"
      with_items: "{{ inventory_hostname_short }}"
      when: realmd_bound|failed

    - name: Add ad group to sudoers
      lineinfile:
        dest: /etc/sudoers
        line: '%g_Server.{{ item }}-sudo@safdal.se        ALL=(ALL)       ALL'
        insertafter: '^%wheel'
      with_items: "{{ inventory_hostname_short }}"
      when: realmd_bound|failed

  handlers:
    - name: restart sssd
      service:
        name: sssd
        state: restarted

First of we have the vars_prompt to keep the password for the domain joining account safe. When you execute the playbook you will get prompted for the password to be typed in. By using the option private: yes the characters will not be echoed on the screen. I have hardcoded the domain join user in the playbook. Just add an additional variable to the vars_prompt if you want.

The next section will install all of the required applications if not already installed. When the command for the actual domain join is executed it will prompt for the password of the specified user account. To get ansible to echo the password back to the prompt we will use pexpect. Which explains why it being installed by pip.

To make the playbook idempotent we first check to see if the machine already is "bound" to a domain. If it is bound to a domain nothing further will happen. If the machine is not bound we will continue with the process of joining it to the domain. You might be wondering why shell: is used instead of command:, the reason for that is that shell: allows us to use functions like pipe. Read more about it here.

Next up we have the actual joining to the domain. Please edit this line to match your needs and environment:

command: /bin/bash -c "/usr/sbin/realm join --user=ADMINISTRATOR@safdal.se --computer-ou=OU=Linux,OU=Servers,OU=Safdal,DC=safdal,DC=se safdal.se"

The playbook will utilize lineinfile to make changes to the sssd.conf. Edit this line to match your domain name:

line: 'default_domain_suffix = safdal.se'

Lastly we have the, in my mind, most useful section. This is where logon access and sudo ability is restricted by using security groups from the AD.

command: /bin/bash -c "/usr/sbin/realm permit -g g_Server.{{ item }}-logon@safdal.se"

As we permit the specified group none other than the members in this group will be able to for example ssh to the machine. As I stated earlier I used this playbook to bulk join servers so go ahead and remove the with_items if not applicable in your use case.

The same goes for the adding of security group(s) to sudoers.

line: '%g_Server.{{ item }}-sudo@safdal.se ALL=(ALL) ALL'

I have verified that both %groupname@domain.tld and %DOMAIN.TLD\\groupname works. It's like tabs vs. spaces, up to you.

When your CentOS server is properly joined to the domain you can use AD credentials to logon to it. And if you're lucky you even got sudo rights.

ssh username@domain.tld@ip-or-fqdn-to-server

This is a working, albeit not flawless, playbook. If you have comments or suggestions feel free to contribute. (Dodgydude #2 must implement a comment section first, tho.)