Created
March 6, 2012 01:47
-
-
Save jumanjiman/1982804 to your computer and use it in GitHub Desktop.
verify if host is properly joined to AD domain
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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