Add Linux Server to Active Directory Domain with Ansible
Julian
Posted on August 26, 2023
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 for CentOS and this 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 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:
- 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 profile.
Posted on August 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.