When automating domain joins with SaltStack, we frequently encounter situations where we need to verify whether a Linux machine (CentOS 6.6/7 in our case) has already been successfully joined to our Active Directory (2008R2 functional level) using adcli join
. While repeated joins don't break functionality, they introduce unnecessary overhead during provisioning.
Many administrators suggest checking for /etc/krb5.keytab
existence, but this approach has limitations:
# Not reliable as a standalone check
if [ -f "/etc/krb5.keytab" ]; then
echo "Keytab exists, but doesn't guarantee active domain join"
fi
The keytab file persists even after machine account removal from AD, making it an unreliable indicator of current domain membership.
Here's a robust method that combines several checks:
#!/bin/bash
# 1. Check keytab existence (basic first check)
if [ ! -f "/etc/krb5.keytab" ]; then
echo "Machine not domain-joined (missing keytab)"
exit 1
fi
# 2. Verify we can get a TGT
if ! klist -k /etc/krb5.keytab | grep -q "HOST/$(hostname -s)"; then
echo "Keytab doesn't contain host principal"
exit 1
fi
# 3. Test actual AD communication
if ! adcli testjoin; then
echo "adcli reports machine is not properly joined"
exit 1
fi
echo "Machine is successfully domain-joined"
exit 0
For SaltStack deployments, we can create a custom state module:
# File: _states/adcheck.py
import os
def is_domain_joined():
"""
Verify domain join status
"""
if not os.path.exists('/etc/krb5.keytab'):
return False
# Additional verification steps
if os.system('adcli testjoin >/dev/null 2>&1') != 0:
return False
return True
For systems using realmd
, we can leverage:
realm list --name-only
This provides clear output about the current domain membership status.
Special considerations when:
- DNS isn't properly configured
- Time synchronization is off
- AD connectivity is intermittent
We can enhance our verification script to handle these scenarios:
# Check time synchronization
if ! ntpdate -q $(grep ^server /etc/ntp.conf | head -1 | awk '{print $2}') >/dev/null; then
echo "Time synchronization issue detected"
fi
For large-scale deployments, cache the verification results:
# Cache TTL of 1 hour
CACHE_FILE="/var/cache/ad_status"
CACHE_TTL=3600
if [ -f "$CACHE_FILE" ] && \
[ $(($(date +%s) - $(stat -c %Y "$CACHE_FILE"))) -lt $CACHE_TTL ]; then
cat "$CACHE_FILE"
exit
fi
# Run actual checks and cache results
adcli testjoin > "$CACHE_FILE"
When automating Linux machine enrollment in Active Directory through SaltStack, a common pain point emerges: reliably detecting whether a machine is already domain-joined. The adcli join
command happily reprocesses existing joins, wasting time and creating unnecessary AD operations.
Many administrators first check for /etc/krb5.keytab
existence, but this only indicates historical joins. More robust verification requires checking both local configuration and AD connectivity:
# Basic but insufficient check
if [ -f "/etc/krb5.keytab" ]; then
echo "Keytab exists - possible domain join"
else
echo "Not domain joined"
fi
For CentOS 6.6/7 systems integrated with 2008R2 domains, implement these verification steps:
Method 1: Test Kerberos Authentication
# Verify we can get a Kerberos ticket
if kinit -k -t /etc/krb5.keytab $(hostname -s)${}; then
echo "Successfully authenticated - domain joined"
kdestroy
else
echo "Failed to authenticate - not properly joined"
fi
Method 2: Query AD Directly
# Check computer object exists in AD
COMPUTER_NAME=$(hostname -s)
DOMAIN=$(hostname -d | tr '[a-z]' '[A-Z]')
if net ads search "(&(objectClass=computer)(name=$COMPUTER_NAME))" \
-P | grep -q "dn: CN=$COMPUTER_NAME"; then
echo "Computer object exists in AD"
else
echo "No AD computer object found"
fi
For automated deployments, create a custom execution module:
# File: _modules/ad_utils.py
import subprocess
def is_domain_joined():
"""
Returns True if machine is properly domain joined
"""
try:
# Test Kerberos auth
hostname = subprocess.check_output(['hostname', '-s']).decode().strip()
domain = subprocess.check_output(['hostname', '-d']).decode().strip()
principal = f"{hostname}${domain.upper()}"
subprocess.run(
['kinit', '-k', '-t', '/etc/krb5.keytab', principal],
check=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE
)
# Cleanup ticket
subprocess.run(['kdestroy'], check=False)
return True
except subprocess.CalledProcessError:
return False
Consider these scenarios in your verification logic:
- Stale keytabs from previous joins
- DNS resolution failures
- Expired machine account passwords
- Network connectivity issues
For production use, combine multiple verification methods with appropriate error handling:
def robust_domain_check():
checks = [
os.path.exists("/etc/krb5.keytab"),
_test_kerberos_auth(),
_check_ad_object_exists()
]
return all(checks)