Skip to content

Instantly share code, notes, and snippets.

@jumanjiman
Created March 6, 2012 01:47
Show Gist options
  • Save jumanjiman/1982804 to your computer and use it in GitHub Desktop.
Save jumanjiman/1982804 to your computer and use it in GitHub Desktop.
verify if host is properly joined to AD domain
class authconfig::samba {
require authconfig::params
require authconfig::packages
require authconfig::kerberos
require authconfig::ldap
require expect
File {
mode => 0644,
owner => root,
group => root,
}
file {'/etc/samba/smb.conf':
ensure => present,
content => template("${module_name}/smb.conf.erb"),
require => Package['samba-common'],
notify => Exec['join-active-directory'],
}
file {'verify_active_directory':
# this script returns 0 if join is intact
path => '/sbin/verify_active_directory',
owner => root,
group => root,
mode => 0755,
content => template("${module_name}/verify_active_directory.erb"),
require => [ File['/etc/samba/smb.conf'], Package['ise-scripts', 'unldif', 'cyrus-sasl-gssapi'] ],
}
file {'configure_active_directory':
# this script joins or leaves a domain
path => '/sbin/configure_active_directory',
owner => root,
group => root,
mode => 0755,
content => template("${module_name}/configure_active_directory.erb"),
require => [ File['/etc/samba/smb.conf'], Package['ise-scripts', 'unldif', 'cyrus-sasl-gssapi'] ],
}
exec {'join-active-directory':
# join the domain configured in samba.conf
command => '/sbin/configure_active_directory -j',
unless => '/sbin/verify_active_directory',
require => File['configure_active_directory', 'verify_active_directory'],
}
}
#!/bin/bash
# This script can cause a host to join or leave
# the Windows Active Directory domain
# variables
#
# specify a timeout for domain operations
seconds=300
#
# post_join_delay seems to be necessary after joing domain
post_join_delay=30
#
# specify a target OU in which to place machines
# when they are added to AD.
# Example AD structure:
# Computers
# |
# \---Servers
# |
# \---Foo
#target_ou="Computers/Servers/Foo"
#
PROG=$(basename $0)
function usage () {
cat >&2 <<- EOF
Usage: $PROG -[hjl]
-h help
-j join the domain
-l leave the domain
Return code indicates success (0) or failure.
EOF
}
# kinit and klist path depend on krb5 release
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/kerberos/bin
if ! [ -r /etc/ldap.secret ]; then
echo "ERROR: failed to read /etc/ldap.secret." >&2
exit 1
fi
NET=$(which net)
if ! [ -x "$NET" ]; then
echo "ERROR: net command is missing or not executable." >&2
exit 1
fi
EXPECT=$(which expect)
if ! [ -x "$EXPECT" ]; then
echo "ERROR: cannot run expect" >&2
exit 1
fi
if [ $# -eq 0 ]; then
usage
exit 2
fi
while getopts "hjlq" option
do
case $option in
h ) usage; exit 0;;
j ) action="join";;
l ) action="leave";;
* ) usage; exit 2;;
esac
done
password=$(cat /etc/ldap.secret)
# short hostname from facter
my_hostname="<%= hostname -%>"
# where should we create computer accounts?
target_ou="<%= scope.lookupvar('authconfig::params::target_ou') -%>"
# what account do we use for net ads commands?
winbind_acct="<%= scope.lookupvar('authconfig::params::winbind_acct') -%>"
# which realm will we be joining?
my_realm="<%= scope.lookupvar('authconfig::params::host_realm') -%>"
# start searching for host accounts here
search_base=$(echo DC=$(echo ${my_realm} | sed 's/\./,DC=/g'))
[[ -z "$search_base" ]] && { echo "error: undefined search_base"; exit 1; }
[[ -z "$target_ou" ]] && { echo "error: undefined target_ou"; exit 1; }
echo "Please do not kill me; I may be slow" >&2
if ! /bin/check_kdc_time; then
echo "ERROR: time offset too large to manipulate domain" >&2
exit 1
else
echo "INFO: time offset seems ok" >&2
fi
if [ "$action" = "leave" ]; then
logger -st $PROG "Leaving AD domain"
$NET ads $action -U ${winbind_acct}%${password} | grep Deleted && success=true || success=false
kdestroy
rm -f /etc/krb5.keytab
if [ $success = "true" ]; then
logger -st $PROG "Left AD domain"
else
logger -st $PROG "Failed to leave AD domain"
fi
fi
ad_settle() {
(
echo -n "Waiting $post_join_delay seconds"
for x in $(seq 1 $post_join_delay); do
echo -n "."
sleep 1
done
echo
) >&2
}
# ldapmodify _does_ use the env var for sasl bind
export KRB5CCNAME=$(umask 0077; mktemp -q winbind_cache.XXXXXXXX)
if [ "$action" = "join" ]; then
logger -st $PROG "Joining AD domain" >&2
$NET ads $action -U ${winbind_acct}%${password} createcomputer="${target_ou}" \
| grep Joined && success=true || success=false
if [ $success = "false" ]; then
echo ERROR: failed to join domain >&2
exit 2
fi
max_attempts=5
for attempt in $(seq 1 $max_attempts); do
devptstype=$(/usr/bin/stat -f -c %T /dev/pts)
if [[ ${devptstype} != devpts ]]; then
/bin/mount -t devpts devpts /dev/pts || {
RC=$?
break
}
fi
echo "$attempt of $max_attempts:"
ad_settle
echo "Getting TGT for ${winbind_acct}@${my_realm}" >&2
$EXPECT -c "spawn -noecho kinit -c $KRB5CCNAME ${winbind_acct}@${my_realm};
expect :;
send ${password}\n;
expect eof"
klist -c $KRB5CCNAME &> /dev/null && break
done
if [[ $RC -ne 0 ]]; then
logger -st $PROG "WARNING: failed to get a ${winbind_acct} TGT. You may want to try leaving the domain, then re-joining"
success=false
else
# change the gssapi delegation flag for the computer account
ldap_servers=$(
grep '^[[:space:]]*password server.*=' /etc/samba/smb.conf \
| sed 's/^.*=[[:space:]]*//' | sed 's/\([^ ]\+\)/ldap:\/\/\1/g'
)
echo "Searching LDAP for $my_hostname computer object" >&2
echo " ldap_servers=$ldap_servers"
echo " search_base=$search_base"
echo " my_hostname=$my_hostname"
ldapsearch -LLL -b ${search_base} -H "${ldap_servers}" \
'(&(objectclass=computer)(name='$my_hostname'))' \
dn \
userAccountControl 2> /dev/null \
| /usr/bin/unldif.sed
echo
output=$(
ldapsearch -LLL -b ${search_base} -H "${ldap_servers}" \
'(&(objectclass=computer)(name='$my_hostname'))' \
dn \
userAccountControl 2> /dev/null \
| /usr/bin/unldif.sed \
| egrep '(^dn|^userAccountControl)'
)
if [ "x$output" = "x" ]; then
logger -st $PROG "Error: ldapsearch failed to find dn or userAccountControl"
success=false
else
# get my distinguished name, which may contain spaces
dn="$(echo -e "${output}" | awk -F: '/^dn:/{print $2}' | sed 's/^[[:space:]]*\(.*\)/\1/')"
# get the current mask
mask=$(awk '/^userAccountControl/{print $2}' <<< "$output")
# calculate new bitmask
delegation_flag=524288
new_mask=$(perl -e "print $mask | $delegation_flag")
# check if its already set
if [ $new_mask -ne $mask ]; then
# modify the bitmask
ldapmodify -H "${ldap_servers}" <<- EOF
dn: $dn
replace: userAccountControl
userAccountControl: $new_mask
EOF
if [ $? -ne 0 ]; then
success=false
echo "ERROR: failed to set GSSAPI delegation on $my_hostname using $dc" >&2
fi
fi
fi
fi
# configure ssh client for gssapi delegation
grep -qi 'gssapidelegatecredentials yes' /etc/ssh/ssh_config
if [ $? -ne 0 ]; then
# assume last stanza in $file is "Host *"
echo -e '\tGSSAPIDelegateCredentials yes' >> /etc/ssh/ssh_config
fi
# configure ssh client to use ipv4 only
grep -qi 'addressfamily inet' /etc/ssh/ssh_config
if [ $? -ne 0 ]; then
# assume last stanza in $file is "Host *"
echo -e '\tAddressFamily inet' >> /etc/ssh/ssh_config
fi
# configure sshd server to use ipv4 only
grep -qi 'addressfamily inet' /etc/ssh/sshd_config
if [ $? -ne 0 ]; then
echo -e 'AddressFamily inet' >> /etc/ssh/sshd_config
fi
# configure sshd server to allow kerberized auth
grep -qi '^kerberosauthentication yes' /etc/ssh/sshd_config
if [ $? -ne 0 ]; then
echo -e 'KerberosAuthentication yes' >> /etc/ssh/sshd_config
fi
# force a restart to pick up new settings
/sbin/service sshd restart || :
# get rid of cred cache
kdestroy -c $KRB5CCNAME &> /dev/null
rm -f $KRB5CCNAME &> /dev/null || :
fi
[ "$success" = "true" ] && exit 0 || exit 1
#!/bin/bash
PROG=$(basename $0)
export EXPIRATION=90
# kinit and klist path depend on krb5 release
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/kerberos/bin
if ! [ -r /etc/ldap.secret ]; then
echo "ERROR: failed to read /etc/ldap.secret." >&2
exit 1
fi
EXPECT=$(which expect)
if ! [ -x "$EXPECT" ]; then
echo "ERROR: cannot run expect" >&2
exit 1
fi
if ! check_kdc_time; then
{
echo "===================================="
echo "WARNING: time offset seems too large"
echo "===================================="
} >&2
fi
password=$(cat /etc/ldap.secret)
# short hostname from facter
my_hostname="<%= hostname -%>"
winbind_acct="<%= scope.lookupvar('authconfig::params::winbind_acct') -%>"
# check for a valid keytab first; if it's not there,
# we can avoid hitting the network
klist -k &> /dev/null || { echo "error: missing keytab"; exit 1; }
# try to get a ticket using the principle HOSTNAME$@REALM
# if this fails, you're not going to work in AD
default_realm=$(grep -i '^[[:space:]]*realm.*=' /etc/samba/smb.conf | sed 's/ //g' | sed 's/realm=//g')
# derive the search base for subsequent LDAP queries from the default realm
search_base=$(echo DC=$(echo ${default_realm} | sed 's/\./,DC=/g'))
# attempt to get host tkt
# see krb5.conf to ensure there is a match between:
# * samba enctypes
# * linux krb5 enctypes
# * AD krb5 enctypes
# example: older versions of AD do not support AES and do not attempt
# to negotiate aes tickets, but current versions do. This is a problem
# if you have a version of samba that does not support aes.
princ=$(tr '[a-z]' '[A-Z]' <<< "$my_hostname")\$@$default_realm
if ! kinit -k $princ; then
logger -st $PROG "Error: failed to kinit -k"
exit 1
fi
# if we're still here, let's try the testjoin
do_testjoin() {
echo "Running net ads testjoin with EXPIRATION=$EXPIRATION" >&2
_cmd="wd net ads testjoin -P"
if [[ -n "$1" ]]; then
_cmd="$_cmd $@"
fi
output=$($_cmd 2>&1)
grep -q 'Join is OK' <<< $output
_rc=$?
if [ $_rc -ne 0 ]; then
logger -st $PROG "Error: net ads testjoin -P failed: $output"
fi
return $_rc
}
do_testjoin
if [ $? -ne 0 ]; then
# get verbose failure info
do_testjoin -d3
fi
# if we're still here, we need to:
# - get a TGT that enables us to query the attribute 'useraccountcontrol'
# - confirm that AD trusts us for GSSAPI delegation
export KRB5CCNAME=$(umask 0077; mktemp -q winbind_cache.XXXXXXXX)
get_tgt() {
(
$EXPECT -c "spawn -noecho kinit -c $KRB5CCNAME ${winbind_acct}@${default_realm};
expect :;
send ${password}\n;
expect eof"
) &> /dev/null
klist -c $KRB5CCNAME &> /dev/null
return $?
}
# try this several times.
max_attempts=5
# assume non-zero for has_tgt
has_tgt=1
for attempt in $(seq 1 $max_attempts); do
# If we just joined the domain, it takes a small amount of time
# for AD to sort things out amongst the DC's, and it
# depends in part on DNS performance.
if get_tgt; then
has_tgt=0
break
fi
echo "." >&2
sleep 3
done
success=true
if [ $has_tgt -ne 0 ]; then
logger -st $PROG "ERROR: failed to get TGT from AD"
success=false
else
# see if credential delegation is enabled
ldap_servers=$(
grep '^[[:space:]]*password server.*=' /etc/samba/smb.conf \
| sed 's/^.*=[[:space:]]*//' | sed 's/\([^ ]\+\)/ldap:\/\/\1/g'
)
[[ ! -z ${ldap_servers} ]] && {
output=$(
ldapsearch -LLL -b ${search_base} -H "${ldap_servers}" \
'(&(objectclass=computer)(name='$my_hostname'))' \
dn \
userAccountControl 2> /dev/null \
| /usr/bin/unldif.sed \
| egrep '(^dn|^userAccountControl)'
)
}
if [ "x$output" = "x" ]; then
echo "Error: failed to get output (dn or userAccountControl) from ldapsearch" >&2
success=false
else
# get my distinguished name, which may contain spaces
dn="$(echo -e "${output}" | awk -F: '/^dn:/{print $2}' | sed 's/^[[:space:]]*\(.*\)/\1/')"
# get the current mask
mask=$(awk '/^userAccountControl/{print $2}' <<< "$output")
# calculate new bitmask
delegation_flag=524288
new_mask=$(perl -e "print $mask | $delegation_flag")
# check if its already set
[ $new_mask -ne $mask ] && {
success=false
echo "ERROR: $my_hostname is not trusted for GSSAPI delegation" >&2
}
fi
# get rid of cred cache
kdestroy -c $KRB5CCNAME &> /dev/null
fi
/bin/verify_reverse_dns || {
success="false"
echo "Broken reverse DNS means delegated authentication will fail" >&2
}
[[ $success == "false" ]] && exit 1
exit 0
@razorsedge
Copy link

@jumanjiman
You wouldn't happen to have the rest of this module available online? Perhaps on the Forge? It seems to be exactly what I am looking for.
https://groups.google.com/d/msg/puppet-users/ooERVgu90gs/2uGnHbfzY9gJ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment