Add Linux Server to Active Directory Domain with Ansible
Tags: ansible, linux, automation, tutorial
Published: 2023-08-26 03:29:25

If you're a new Systems Administrator that faces the challenge of creating local users for every Linux server manually on your infrastructure, this post is for you.

Imagine you´re new in a place where you have to manage 30+ Linux servers and nobody else knows how to create users and give others access. Creating users in each and every single one of them is a headache right?

Well, that led me to use a time-saving approach where creating users becomes a task of the actual responsible department not mine and, to make the hardening of your infrastructure more robust.

Lacking of everything because of budget, I decided to manually add the servers to an Active Directory domain and restrict the access to an specific group of users. With that solution, security department only needs to add a user to that group and that's it.

First, I researched on how to add Linux Servers to an Active Directory domain and I found [this](https://www.linuxtechi.com/integrate-rhel7-centos7-windows-active-directory/) for CentOS and [this](https://codymoler.github.io/tutorials/ubuntu-active-directory/) for Debian/Ubuntu distros.

After doing a couple of them, I decided to create an Ansible playbook out of these guides because if I didn´t, I´d be still adding servers to the domain at the time of writing this.

To do so, I basically created two playbooks in one, and they were executed depending on the distro of the current target server.

## Preparing before the playbook

Before anything, I created a vault where I stored these variables:

- The Linux OS credentials

- The Windows Account with rights to add servers to the domain

- The domain name

- The domain controller´s name

- The domain controllers´s IP

- The Active Directory user group allowed to connect to the servers.

I created the vault using the command `ansible-vault create FILENAME --vault-password-file PASSWORDFILE` where `FILENAME` is the actual vault that contains the Ansible variables and `PASSWORDFILE` is a text file that only contains the password for unlocking the vault.

After that, you can edit the vault using `ansible-vault edit FILENAME --vault-password-file PASSWORDFILE` and add the following lines to it:

```
server_username: linuxuser
server_password: linuxpassword
domain_name: example.do
domain_controller_ip: X.X.X.X
domain_controller_name: controller
domain_admin_user: adminuser
domain_admin_password: adminpassword
domain_group_allowed: allowedgroup
```

**Note: The Linux user needs root permissions and the domain name should be all caps.**

After having your vault correctly created, you have to add the target servers in your hosts file that resides by default in `/etc/ansible/hosts`.

In the hosts file for this, I used the following structure:

```
[Group_name]
SERVER1_HOSTNAME ansible_host=1.1.1.1
SERVER2_HOSTNAME ansible_host=2.2.2.2
.....
```

You can group the servers as you want, but because of how the playbook is made, you won't need more than one group for this to work.

## The Playbook
After creating our vault with our variables, and having all of our target servers in the hosts file, we can start creating our playbook. First, we name our playbook and configure the parameters as shown:

```
---
- name: Add Linux Server to Domain
  gather_facts: true
  hosts: Group_name
  vars:
    ansible_become: yes
    ansible_become_method: sudo
    ansible_user: "{{ server_username  }}"
    ansible_ssh_pass: "{{ server_password  }}"
    ansible_become_pass: "{{ server_password  }}"

    credentials: {
               'username': "{{ domain_admin_user }}",
               'password': "{{ domain_admin_password }}"
              }
    domain_name: "{{ domain_name }}"
    domain_controller_ip: "{{ domain_controller_ip }}"
    domain_controller_name: "{{ domain_controller_name }}"
    domain_group_allowed: "{{ domain_group_allowed }}"
```

In this part, we name our playbook and set the gather facts to true so we can use them later to take decisions. We also define the target hosts group that this playbook will be executed on and then, we reference our variables. The variables that start with `ansible_` are default variables, that don't need to be referenced later because they define values used by Ansible functions like elevating privileges (sudo). The following variables have the values that we specified on our vault file, in this example I used the same names in the vault and in the Ansible playbook but this is not necessary, but yo have to be carefull so you don't misplace any value.

Then we create our tasks

```
  tasks:

    - name: Install CentOS Packages
      yum:
        name:
          - sssd
          - realmd
          - oddjob
          - oddjob-mkhomedir
          - adcli
          - samba-common
          - samba-common-tools
          - krb5-workstation
          - openldap-clients
          - policycoreutils-python
        state: present
      when: ansible_distribution == "CentOS"

    - name: Update Hosts File
      lineinfile:
        dest: "/etc/hosts"
        line: "{{ domain_controller_ip }}   {{ domain_controller_name }}.{{ domain_name }}   {{ domain_controller_name }}"
        state: present
        backup: yes
      when: ansible_distribution == "CentOS"

    - name: Join Realm
      shell:
        "echo '{{ credentials['password'] }}' | sudo realm join --user={{ credentials['username'] }} {{ domain_name }}"
      register: result
      when: ansible_distribution == "CentOS"
    - name: Print Realm Result
      debug: var=result.stdout_lines
      when: ansible_distribution == "CentOS"

    - name: Modify SSDCONF Line 1
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'use_fully_qualified_names = True'
        replace: 'use_fully_qualified_names = False'
      when: ansible_distribution == "CentOS"
    - name: Modify SSDCONF Line 2
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'fallback_homedir = /home/%u@%d'
        replace: 'fallback_homedir = /home/%u'
      when: ansible_distribution == "CentOS"

    - name: Restart sssd
      shell:
        "sudo systemctl restart sssd"
      when: ansible_distribution == "CentOS"
    - name: Daemon Reload
      shell:
        "sudo systemctl daemon-reload"
      when: ansible_distribution == "CentOS"

    - name: Add {{ domain_group_allowed }} to Sudoers
      lineinfile:
        dest: "/etc/sudoers"
        line: "%{{ domain_group_allowed }}   ALL=(ALL)       NOPASSWD: ALL"
        state: present
        backup: yes
      when: ansible_distribution == "CentOS"

    - name: Allow Only {{ domain_group_allowed }} CentOs Login
      shell:
        "realm permit -g {{ domain_group_allowed }}@{{ domain_name }}"
      when: ansible_distribution == "CentOS"
```

This first group of tasks correspond to the ones that will run on CentOS distros. In this case, we basically follow step by step the [guide](https://www.linuxtechi.com/integrate-rhel7-centos7-windows-active-directory/) for adding a CentOS server to active directory using whatever Ansible has to solve this. At the end of each step, there's a condition that specifies that each task will run in the current target server only if it's OS distribution is equal to the condition, being CentOS in this case.

The same way, the Ubuntu tasks were made following the previous [guide](https://codymoler.github.io/tutorials/ubuntu-active-directory/):

```
    - name: Apt Update
      shell:
        "sudo apt-get update -y"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Install Debian/Ubuntu Packages
      apt:
        name:
          - sssd-ad
          - sssd-tools
          - realmd
          - adcli
          - packagekit
        state: present
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Create KRB5 File
      copy:
        dest: "/etc/krb5.conf"
        content: |
          [libdefaults]
          default_realm = {{ domain_name }}
          ticket_lifetime = 24h
          renew_lifetime = 7d
          dns_lookup_realm = false
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Install Debian/Ubuntu Remaining Packages
      apt:
        name:
          - krb5-user
          - sssd-krb5
        state: present
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Modify Hosts File
      lineinfile:
        dest: "/etc/hosts"
        line: "127.0.1.1       {{ ansible_hostname }}.{{ domain_name }}"
        state: present
        backup: yes
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Modify Hostname
      shell:
        "sudo hostnamectl set-hostname {{ ansible_hostname }}.{{ domain_name }}"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Kinit
      shell:
        "echo {{ credentials['password'] }} | sudo kinit {{ credentials['username'] }}"
      register: result
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Print Kinit Result
      debug: var=result.stdout_lines
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Join Realm Ubuntu
      shell:
        "echo {{ credentials['password'] }} | sudo realm join -v -U {{ credentials['username'] }} {{ domain_name }}"
      register: result
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Print Realm Result
      debug: var=result.stdout_lines
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Activate Homedir Creation
      shell:
        "sudo pam-auth-update --enable mkhomedir"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Modify SSDCONF Line 1 Ubuntu
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'use_fully_qualified_names = True'
        replace: 'use_fully_qualified_names = False'
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Modify SSDCONF Line 2 Ubuntu
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'fallback_homedir = /home/%u@%d'
        replace: 'fallback_homedir = /home/%u'
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Add {{ domain_group_allowed }} to Sudoers Ubuntu
      lineinfile:
        dest: "/etc/sudoers"
        line: "%{{ domain_group_allowed }}   ALL=(ALL) NOPASSWD:ALL"
        state: present
        backup: yes
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Allow Only {{ domain_group_allowed }} Login Ubuntu
      shell:
        "realm permit -g {{ domain_group_allowed }}@{{ domain_name }}"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
```

Then, you can execute the playbook using the following command:

```
ansible-playbook -i [path/to/hostsfile] [path/to/playbookfile] -e@./[path/to/vaultfile]  --vault-password-file ~/[path/to/passwordfile]
```


After executing this, your server will be added to the Active Directory Domain and you'll be ready to handle users in a more secure and efficient way.

I hope this helps someone out there, for me this was something that made my days easier to handle.

The code is available at my [GitHub](https://github.com/julianalmanzar/linuxserver_to_domain) profile.