Add Ansible deployment and systemd-resolved DNS support
This commit is contained in:
parent
605c112d8b
commit
36cf28ce46
25
README.md
25
README.md
@ -33,6 +33,31 @@ This script automates the process of joining an Ubuntu Server to an Active Direc
|
|||||||
7. If the script completes successfully, your server will be joined to the Active Directory domain.
|
7. If the script completes successfully, your server will be joined to the Active Directory domain.
|
||||||
8. You might need to reboot for the authentication with AD credentials to work
|
8. You might need to reboot for the authentication with AD credentials to work
|
||||||
|
|
||||||
|
### Non-interactive usage
|
||||||
|
|
||||||
|
You can also run the script without prompts by setting environment variables:
|
||||||
|
|
||||||
|
- `ADJOIN_HOSTNAME`
|
||||||
|
- `ADJOIN_ADMIN_USER`
|
||||||
|
- `ADJOIN_ADMIN_PASSWORD`
|
||||||
|
- `ADJOIN_DOMAIN_NAME`
|
||||||
|
- `ADJOIN_AD_GROUP`
|
||||||
|
- `ADJOIN_DNS_SERVERS` (preferred; comma/space separated)
|
||||||
|
- `ADJOIN_DNS_SERVER` (backward compatible single server)
|
||||||
|
- `ADJOIN_DNS_INTERFACE` (optional; interface name for `resolvectl`, e.g. `eth0`)
|
||||||
|
|
||||||
|
### DNS management note
|
||||||
|
|
||||||
|
This script needs to be able to set DNS for domain discovery/join. It tries `systemd-resolved` first, then falls back to writing `/etc/resolv.conf`.
|
||||||
|
|
||||||
|
If your DNS is managed by another component (e.g. NetworkManager, netplan, resolvconf, or cloud-init), you may need to adapt DNS configuration for your environment or the join can fail.
|
||||||
|
|
||||||
|
## Ansible
|
||||||
|
|
||||||
|
An Ansible role and example playbook are included under `ansible/` to deploy and run the script non-interactively (all inputs are passed as variables, suitable for Semaphore).
|
||||||
|
|
||||||
|
See `ansible/README.md` for required variables and usage.
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
This script is provided as-is and without any warranty. Use it at your own risk. Be sure to have valid credentials and administrative access to your Active Directory domain before running the script.
|
This script is provided as-is and without any warranty. Use it at your own risk. Be sure to have valid credentials and administrative access to your Active Directory domain before running the script.
|
||||||
|
|||||||
@ -1,24 +1,110 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Prompt the user for the hostname
|
set -euo pipefail
|
||||||
|
|
||||||
|
hostname="${ADJOIN_HOSTNAME:-}"
|
||||||
|
admin_user="${ADJOIN_ADMIN_USER:-}"
|
||||||
|
admin_password="${ADJOIN_ADMIN_PASSWORD:-}"
|
||||||
|
domain_name="${ADJOIN_DOMAIN_NAME:-}"
|
||||||
|
ad_group="${ADJOIN_AD_GROUP:-}"
|
||||||
|
dns_servers_raw="${ADJOIN_DNS_SERVERS:-${ADJOIN_DNS_SERVER:-}}"
|
||||||
|
dns_interface="${ADJOIN_DNS_INTERFACE:-}"
|
||||||
|
|
||||||
|
configure_dns() {
|
||||||
|
local domain="$1"
|
||||||
|
shift
|
||||||
|
local -a servers=("$@")
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet systemd-resolved; then
|
||||||
|
echo "Configuring DNS via systemd-resolved..."
|
||||||
|
sudo mkdir -p /etc/systemd/resolved.conf.d
|
||||||
|
{
|
||||||
|
echo "[Resolve]"
|
||||||
|
echo "DNS=${servers[*]}"
|
||||||
|
echo "Domains=$domain"
|
||||||
|
} | sudo tee /etc/systemd/resolved.conf.d/ad-join.conf >/dev/null
|
||||||
|
sudo systemctl restart systemd-resolved
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v resolvectl >/dev/null 2>&1; then
|
||||||
|
local iface="$dns_interface"
|
||||||
|
if [ -z "$iface" ] && command -v ip >/dev/null 2>&1; then
|
||||||
|
iface="$(ip route show default 2>/dev/null | awk '{print $5; exit}')"
|
||||||
|
fi
|
||||||
|
if [ -n "$iface" ]; then
|
||||||
|
echo "Configuring DNS via resolvectl on interface: $iface"
|
||||||
|
sudo resolvectl dns "$iface" "${servers[@]}"
|
||||||
|
sudo resolvectl domain "$iface" "$domain"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Configuring DNS via /etc/resolv.conf..."
|
||||||
|
{
|
||||||
|
for server in "${servers[@]}"; do
|
||||||
|
echo "nameserver $server"
|
||||||
|
done
|
||||||
|
} | sudo tee /etc/resolv.conf >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$hostname" ]; then
|
||||||
read -p "Enter the hostname for the server: " hostname
|
read -p "Enter the hostname for the server: " hostname
|
||||||
echo
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
# Prompt the user for the necessary information
|
# Prompt the user for the necessary information
|
||||||
|
if [ -z "$admin_user" ]; then
|
||||||
read -p "Administrator username (AdminUser): " admin_user
|
read -p "Administrator username (AdminUser): " admin_user
|
||||||
|
fi
|
||||||
|
if [ -z "$admin_password" ]; then
|
||||||
read -s -p "Administrator password: " admin_password
|
read -s -p "Administrator password: " admin_password
|
||||||
echo # To move to the next line
|
echo # To move to the next line
|
||||||
|
fi
|
||||||
|
if [ -z "$domain_name" ]; then
|
||||||
read -p "Active Directory domain name: " domain_name
|
read -p "Active Directory domain name: " domain_name
|
||||||
|
fi
|
||||||
|
if [ -z "$ad_group" ]; then
|
||||||
read -p "Active Directory group for sudo access: " ad_group
|
read -p "Active Directory group for sudo access: " ad_group
|
||||||
|
fi
|
||||||
|
|
||||||
# Prompt for DNS server IP and verify DNS resolution
|
# Prompt for DNS server IP(s) and verify DNS resolution
|
||||||
|
if [ -z "$dns_servers_raw" ]; then
|
||||||
while true; do
|
while true; do
|
||||||
read -p "DNS server IP: " dns_server
|
read -p "DNS server IP(s) (comma or space separated): " dns_servers_raw
|
||||||
if nslookup $domain_name $dns_server; then
|
dns_servers_raw="${dns_servers_raw//,/ }"
|
||||||
|
read -r -a dns_servers <<<"$dns_servers_raw"
|
||||||
|
|
||||||
|
dns_ok=false
|
||||||
|
for server in "${dns_servers[@]}"; do
|
||||||
|
if nslookup "$domain_name" "$server" >/dev/null 2>&1; then
|
||||||
|
dns_ok=true
|
||||||
break
|
break
|
||||||
else
|
|
||||||
echo "DNS resolution failed. Please enter a valid DNS server IP."
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "$dns_ok" = true ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "DNS resolution failed using provided server(s). Please try again."
|
||||||
|
done
|
||||||
|
else
|
||||||
|
dns_servers_raw="${dns_servers_raw//,/ }"
|
||||||
|
read -r -a dns_servers <<<"$dns_servers_raw"
|
||||||
|
|
||||||
|
dns_ok=false
|
||||||
|
for server in "${dns_servers[@]}"; do
|
||||||
|
if nslookup "$domain_name" "$server" >/dev/null 2>&1; then
|
||||||
|
dns_ok=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$dns_ok" != true ]; then
|
||||||
|
echo "DNS resolution failed using provided server(s): ${dns_servers[*]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Install the necessary packages with a loading bar
|
# Install the necessary packages with a loading bar
|
||||||
echo "Installing required packages..."
|
echo "Installing required packages..."
|
||||||
apt -y install realmd sssd sssd-tools libnss-sss libpam-sss adcli samba-common-bin oddjob oddjob-mkhomedir packagekit
|
apt -y install realmd sssd sssd-tools libnss-sss libpam-sss adcli samba-common-bin oddjob oddjob-mkhomedir packagekit
|
||||||
@ -26,20 +112,16 @@ apt -y install realmd sssd sssd-tools libnss-sss libpam-sss adcli samba-common-b
|
|||||||
echo "Changing the hostname to: $hostname.$domain_name"
|
echo "Changing the hostname to: $hostname.$domain_name"
|
||||||
|
|
||||||
# Change the hostname
|
# Change the hostname
|
||||||
hostnamectl set-hostname $hostname
|
hostnamectl set-hostname "$hostname.$domain_name"
|
||||||
echo "$hostname.$domain_name" | sudo tee /etc/hostname
|
echo "$hostname.$domain_name" | sudo tee /etc/hostname
|
||||||
|
|
||||||
# Change the DNS server settings in /etc/resolv.conf
|
# Change DNS settings
|
||||||
echo "Changing DNS server to: $dns_server"
|
echo "Setting DNS server(s) to: ${dns_servers[*]}"
|
||||||
echo "nameserver $dns_server" | sudo tee /etc/resolv.conf
|
configure_dns "$domain_name" "${dns_servers[@]}"
|
||||||
|
|
||||||
# Install the necessary packages with a loading bar
|
|
||||||
echo "Installing required packages..."
|
|
||||||
apt -y install realmd sssd sssd-tools libnss-sss libpam-sss adcli samba-common-bin oddjob oddjob-mkhomedir packagekit & loading_bar
|
|
||||||
|
|
||||||
# Discover the domain and join, registering DNS
|
# Discover the domain and join, registering DNS
|
||||||
echo "Joining the domain and registering DNS..."
|
echo "Joining the domain and registering DNS..."
|
||||||
echo $admin_password | realm join --user=$admin_user $domain_name
|
printf '%s\n' "$admin_password" | realm join --user="$admin_user" "$domain_name"
|
||||||
|
|
||||||
# Modify the sssd.conf configuration to enable dynamic DNS updates
|
# Modify the sssd.conf configuration to enable dynamic DNS updates
|
||||||
echo "Configuring dynamic DNS updates..."
|
echo "Configuring dynamic DNS updates..."
|
||||||
|
|||||||
47
ansible/README.md
Normal file
47
ansible/README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Ansible deployment
|
||||||
|
|
||||||
|
This repo includes an Ansible role that deploys and runs `ad-join-script.sh` in a non-interactive way by passing all inputs as variables.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `ansible/site.yml`: example playbook using the role
|
||||||
|
- `ansible/roles/ad_join`: role that copies and runs the script
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- `ad_join_hostname`: short hostname (without domain)
|
||||||
|
- `ad_join_domain_name`: AD domain (e.g. `corp.example.com`)
|
||||||
|
- `ad_join_admin_user`: account used to join the domain
|
||||||
|
- `ad_join_admin_password`: password for `ad_join_admin_user`
|
||||||
|
- `ad_join_dns_servers`: DNS server IPs used to validate domain resolution (list preferred; also accepts a comma-separated string)
|
||||||
|
- `ad_join_dns_server`: single DNS server IP (backward compatible)
|
||||||
|
- `ad_join_ad_group`: AD group to grant sudo access (written to `/etc/sudoers`)
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
- `ad_join_force` (default: `false`): run even if `realm list` already shows the domain
|
||||||
|
- `ad_join_run` (default: `true`): set to `false` to only deploy the script
|
||||||
|
|
||||||
|
## Semaphore setup notes
|
||||||
|
|
||||||
|
- Point Semaphore to the playbook at `ansible/site.yml`.
|
||||||
|
- Define the variables above in the task template (use a secret variable for `ad_join_admin_password`).
|
||||||
|
|
||||||
|
## Run (example)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory.ini ansible/site.yml \
|
||||||
|
-e ad_join_hostname=ubuntuhost01 \
|
||||||
|
-e ad_join_domain_name=corp.example.com \
|
||||||
|
-e ad_join_admin_user=JoinUser \
|
||||||
|
-e ad_join_admin_password='***' \
|
||||||
|
-e 'ad_join_dns_servers=["192.0.2.53","192.0.2.54"]' \
|
||||||
|
-e ad_join_ad_group='LinuxAdmins'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important behavior
|
||||||
|
|
||||||
|
- The script attempts to configure DNS via `systemd-resolved` first, then falls back to writing `/etc/resolv.conf`. If DNS is managed elsewhere, you may need to adapt DNS configuration for your environment.
|
||||||
|
- The role runs the join step only when the host is not already joined (unless `ad_join_force: true`).
|
||||||
26
ansible/roles/ad_join/defaults/main.yml
Normal file
26
ansible/roles/ad_join/defaults/main.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
ad_join_script_src: "{{ playbook_dir }}/../ad-join-script.sh"
|
||||||
|
ad_join_script_dest: /usr/local/sbin/ubuntu-ad-join.sh
|
||||||
|
|
||||||
|
ad_join_install_packages:
|
||||||
|
- realmd
|
||||||
|
- sssd
|
||||||
|
- sssd-tools
|
||||||
|
- libnss-sss
|
||||||
|
- libpam-sss
|
||||||
|
- adcli
|
||||||
|
- samba-common-bin
|
||||||
|
- oddjob
|
||||||
|
- oddjob-mkhomedir
|
||||||
|
- packagekit
|
||||||
|
|
||||||
|
ad_join_hostname: ""
|
||||||
|
ad_join_admin_user: ""
|
||||||
|
ad_join_admin_password: ""
|
||||||
|
ad_join_domain_name: ""
|
||||||
|
ad_join_ad_group: ""
|
||||||
|
ad_join_dns_server: ""
|
||||||
|
ad_join_dns_servers: []
|
||||||
|
|
||||||
|
ad_join_force: false
|
||||||
|
ad_join_run: true
|
||||||
70
ansible/roles/ad_join/tasks/main.yml
Normal file
70
ansible/roles/ad_join/tasks/main.yml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
- name: Validate required variables
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ad_join_hostname | length > 0
|
||||||
|
- ad_join_domain_name | length > 0
|
||||||
|
- ad_join_admin_user | length > 0
|
||||||
|
- ad_join_admin_password | length > 0
|
||||||
|
- ad_join_ad_group | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Missing required variables. Pass them as extra vars (Semaphore) or via inventory/group vars.
|
||||||
|
|
||||||
|
- name: Normalize DNS servers list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
ad_join_dns_servers_effective: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
ad_join_dns_servers.split(',') | map('trim') | reject('equalto', '') | list
|
||||||
|
)
|
||||||
|
if (ad_join_dns_servers is string)
|
||||||
|
else (ad_join_dns_servers | default([]))
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Back-compat for single DNS variable
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
ad_join_dns_servers_effective: "{{ [ad_join_dns_server] }}"
|
||||||
|
when:
|
||||||
|
- ad_join_dns_servers_effective | length == 0
|
||||||
|
- ad_join_dns_server | length > 0
|
||||||
|
|
||||||
|
- name: Validate DNS server(s) provided
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ad_join_dns_servers_effective | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Missing DNS server(s). Set ad_join_dns_servers (preferred) or ad_join_dns_server.
|
||||||
|
|
||||||
|
- name: Install required packages
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ ad_join_install_packages }}"
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
- name: Check current domain join status
|
||||||
|
ansible.builtin.command: realm list
|
||||||
|
register: ad_join_realm_list
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Deploy AD join script
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ ad_join_script_src }}"
|
||||||
|
dest: "{{ ad_join_script_dest }}"
|
||||||
|
mode: "0750"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Join domain using script
|
||||||
|
ansible.builtin.command: "{{ ad_join_script_dest }}"
|
||||||
|
environment:
|
||||||
|
ADJOIN_HOSTNAME: "{{ ad_join_hostname }}"
|
||||||
|
ADJOIN_ADMIN_USER: "{{ ad_join_admin_user }}"
|
||||||
|
ADJOIN_ADMIN_PASSWORD: "{{ ad_join_admin_password }}"
|
||||||
|
ADJOIN_DOMAIN_NAME: "{{ ad_join_domain_name }}"
|
||||||
|
ADJOIN_AD_GROUP: "{{ ad_join_ad_group }}"
|
||||||
|
ADJOIN_DNS_SERVERS: "{{ ad_join_dns_servers_effective | join(' ') }}"
|
||||||
|
no_log: true
|
||||||
|
when:
|
||||||
|
- ad_join_run | bool
|
||||||
|
- ad_join_force | bool or (ad_join_realm_list.stdout is not regex_search('realm-name:\\s*' ~ (ad_join_domain_name | regex_escape) ~ '\\b'))
|
||||||
9
ansible/site.yml
Normal file
9
ansible/site.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
- name: Join Ubuntu hosts to Active Directory
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: ad_join
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user