diff options
-rw-r--r-- | README.md | 28 | ||||
-rw-r--r-- | manifests/init.pp | 4 | ||||
-rw-r--r-- | manifests/server.pp | 58 | ||||
-rw-r--r-- | manifests/server/ads.pp | 116 | ||||
-rw-r--r-- | manifests/server/share.pp | 5 | ||||
-rw-r--r-- | manifests/server/winbind.pp | 16 | ||||
-rw-r--r-- | templates/configure_active_directory.erb | 142 | ||||
-rw-r--r-- | templates/verify_active_directory.erb | 107 |
8 files changed, 431 insertions, 45 deletions
@@ -43,6 +43,34 @@ Tweak and add the following to your site manifest: } } +If you want join Samba server to Active Directory. Tested on Ubuntu 12.04. + + node 'server.example.com' { + class {'samba::server': + workgroup => 'example', + server_string => "Example Samba Server", + interfaces => "eth0 lo", + security => 'ads' + } + + samba::server::share {'ri-storage': + comment => 'RBTH User Storage', + path => "$smb_share", + browsable => true, + writable => true, + create_mask => 0770, + directory_mask => 0770, + } + + class { 'samba::server::ads': + winbind_acct => $::domain_admin, + winbind_pass => $::admin_password, + realm => 'EXAMPLE.COM', + nsswitch => true, + target_ou => "Nix_Mashine" + } + } + Most configuration options are optional. ## Contributing diff --git a/manifests/init.pp b/manifests/init.pp index c71ee1d..8a914a4 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,3 +1,7 @@ class samba { include samba::server + + if samba::server::security == 'ads' { + include samba::server::ads + } }
\ No newline at end of file diff --git a/manifests/server.pp b/manifests/server.pp index cc1a951..9b6d3c0 100644 --- a/manifests/server.pp +++ b/manifests/server.pp @@ -18,53 +18,27 @@ class samba::server($interfaces = '', notify => Class['samba::server::service'] } - augeas { 'global-interfaces': - context => $context, - changes => $interfaces ? { - default => ["set \"${target}/interfaces\" '${interfaces}'", "set \"${target}/bind interfaces only\" yes"], - '' => ["rm \"${target}/interfaces\"", "rm \"${target}/bind interfaces only\""], - }, - require => Augeas['global-section'], - notify => Class['samba::server::service'] - } - augeas { 'global-security': - context => $context, - changes => $security ? { - default => "set \"${target}/security\" '${security}'", - '' => "rm \"${target}/security\"", - }, - require => Augeas['global-section'], - notify => Class['samba::server::service'] - } - - augeas { 'global-server_string': - context => $context, - changes => $server_string ? { - default => "set \"${target}/server string\" '${server_string}'", - '' => "rm \"${target}/server string\"", - }, - require => Augeas['global-section'], - notify => Class['samba::server::service'] + set_samba_option { + 'bind interfaces only': value => 'yes'; + 'security': value => $security; + 'server string': value => $server_string; + 'unix password sync': value => $unix_password_sync; + 'workgroup': value => $workgroup; } +} - augeas { 'global-unix_password_sync': - context => $context, - changes => $unix_password_sync ? { - default => "set \"${target}/unix password sync\" '$unix_password_sync'", - '' => "rm \"${target}/unix_password_sync\"", - }, - require => Augeas['global-section'], - notify => Class['samba::server::service'] +define set_samba_option ( $value = '', $signal = 'samba::server::service' ) { + $context = $samba::server::context + $target = $samba::server::target + $changes = $value ? { + default => "set \"${target}/$name\" $value", + '' => "rm ${target}/$name", } - - augeas { 'global-workgroup': + augeas { "samba-$name": context => $context, - changes => $workgroup ? { - default => "set ${target}/workgroup '${workgroup}'", - '' => "rm ${target}/workgroup", - }, + changes => $changes, require => Augeas['global-section'], - notify => Class['samba::server::service'] + notify => Class[$signal] } } diff --git a/manifests/server/ads.pp b/manifests/server/ads.pp new file mode 100644 index 0000000..1f8e602 --- /dev/null +++ b/manifests/server/ads.pp @@ -0,0 +1,116 @@ +# This module join samba server to Active Dirctory +# +# Copyright (c) 2013 Lebedev Vadim, abraham1901 at g mail dot c o m +# Licensed under the MIT License, http://opensource.org/licenses/MIT + +class samba::server::ads($ensure = present, + $winbind_acct = 'admin', + $winbind_pass = 'SecretPass', + $realm = 'domain.com', + $winbind_uid = '10000-20000', + $winbind_gid = '10000-20000', + $winbind_enum_groups = 'yes', + $winbind_enum_users = 'yes', + $winbind_use_default_domain = 'yes', + $nsswitch = false, + $acl_group_control = 'yes', + $map_acl_inherit = 'yes', + $inherit_acls = 'yes', + $store_dos_attributes = 'yes', + $ea_support = 'yes', + $dos_filemode = 'yes', + $acl_check_permissions = false, + $map_system = 'no', + $map_archive = 'no', + $map_readonly = 'no', + $target_ou = 'Nix_Mashine') { + + package{ + 'krb5-user': ensure => installed; + 'winbind': ensure => installed; + 'expect': ensure => installed; + } + + include samba::server::config + include samba::server::winbind + + $signal = 'samba::server::winbind' + + set_samba_option { + 'realm': value => $realm, + signal => $signal; + 'winbind uid': value => $winbind_uid, + signal => $signal; + 'winbind gid': value => $winbind_gid, + signal => $signal; + 'winbind enum groups': value => $winbind_enum_groups, + signal => $signal; + 'winbind enum users': value => $winbind_enum_users, + signal => $signal; + 'winbind use default domain': value => $winbind_use_default_domain, + signal => $signal; + 'acl group control': value => $acl_group_control; + 'map acl inherit': value => $map_acl_inherit; + 'inherit acls': value => $inherit_acls; + 'store dos attributes': value => $store_dos_attributes; + 'ea support': value => $ea_support; + 'dos filemode': value => $dos_filemode; + 'acl check permissions': value => $acl_check_permissions; + 'map system': value => $map_system; + 'map archive': value => $map_archive; + 'map readonly': value => $map_readonly; + } + + $nss_file='etc/nsswitch.conf' + + $changes=$nsswitch ? { + true => [ + "set database[. = 'passwd']/service[1] compat", + "set database[. = 'passwd']/service[2] winbind", + "set database[. = 'group']/service[1] compat", + "set database[. = 'group']/service[2] winbind", + ], + false => [ + "rm /files/${nss_file}/database[. = 'passwd']/service[. = 'winbind']", + "rm /files/${nss_file}/database[. = 'group']/service[. = 'winbind']", + ] + } + + augeas { 'nsswitch': + context => "/files/${nss_file}", + changes => $changes + } + + 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 => [ Package['krb5-user', 'winbind', 'expect'], + Augeas['samba-realm', 'samba-security', 'samba-winbind enum users', + 'samba-winbind enum groups', 'samba-winbind uid', 'samba-winbind gid', + 'samba-winbind use default domain'] ], + } + + 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 => [ Package['krb5-user', 'winbind', 'expect'], + Augeas['samba-realm', 'samba-security', 'samba-winbind enum users', + 'samba-winbind enum groups', 'samba-winbind uid', 'samba-winbind gid', + 'samba-winbind use default domain'] ], + } + + 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'], Class['samba::server::winbind'] ], + } +} diff --git a/manifests/server/share.pp b/manifests/server/share.pp index 7d308a0..b4eb02f 100644 --- a/manifests/server/share.pp +++ b/manifests/server/share.pp @@ -15,10 +15,9 @@ define samba::server::share($ensure = present, $read_only = '', $public = '', $writable = '', - $printable = '', - ) { + $printable = '') { - $context = '/files/etc/samba/smb.conf' + $context = $samba::server::context $target = "target[. = '${name}']" augeas { "${name}-section": diff --git a/manifests/server/winbind.pp b/manifests/server/winbind.pp new file mode 100644 index 0000000..76136b9 --- /dev/null +++ b/manifests/server/winbind.pp @@ -0,0 +1,16 @@ +class samba::server::winbind ($ensure = running, $enable = true) { + $service_name = 'winbind' + + notify { 'winbind-service': + message => 'Check winbind service', + } + + service { $service_name: + ensure => $ensure, + hasstatus => true, + hasrestart => true, + enable => $enable, + require => Class['samba::server::config'] + } + +} diff --git a/templates/configure_active_directory.erb b/templates/configure_active_directory.erb new file mode 100644 index 0000000..35ba86f --- /dev/null +++ b/templates/configure_active_directory.erb @@ -0,0 +1,142 @@ +#!/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 +# + +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 + +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="<%= scope.lookupvar('samba::server::ads::winbind_pass') -%>" + +# short hostname from facter +my_hostname="<%= hostname -%>" + +# what account do we use for net ads commands? +winbind_acct="<%= scope.lookupvar('samba::server::ads::winbind_acct') -%>" + +# which realm will we be joining? +my_realm="<%= scope.lookupvar('samba::server::ads::realm') -%>" + +# where should we create computer accounts? +target_ou="<%= scope.lookupvar('samba::server::ads::target_ou') -%>" + +echo "Please do not kill me; I may be slow" >&2 + +#TODO, need write time check check_kdc_time +#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 + 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 [ $(wbinfo -u|wc -l) != 0 ]; then + success=true +else + echo "ERROR: return user list from AD is empty" >&2 + success=false +fi + +# get rid of cred cache +kdestroy -c $KRB5CCNAME &> /dev/null +rm -f $KRB5CCNAME &> /dev/null || : + +fi + +[ "$success" = "true" ] && exit 0 || exit 1 diff --git a/templates/verify_active_directory.erb b/templates/verify_active_directory.erb new file mode 100644 index 0000000..5a2a506 --- /dev/null +++ b/templates/verify_active_directory.erb @@ -0,0 +1,107 @@ +#!/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 + +EXPECT=$(which expect) +if ! [ -x "$EXPECT" ]; then + echo "ERROR: cannot run expect" >&2 + exit 1 +fi + +#TODO +#if ! check_kdc_time; then +# { +# echo "====================================" +# echo "WARNING: time offset seems too large" +# echo "====================================" +# } >&2 +#fi + +password="<%= scope.lookupvar('samba::server::ads::winbind_pass') -%>" + +# short hostname from facter +my_hostname="<%= hostname -%>" + +winbind_acct="<%= scope.lookupvar('samba::server::ads::winbind_acct') -%>" + +default_realm=$(grep -i '^[[:space:]]*realm.*=' /etc/samba/smb.conf | sed 's/ //g' | sed 's/realm=//g') + +# if we're still here, let's try the testjoin +do_testjoin() { + echo "Running net ads testjoin with EXPIRATION=$EXPIRATION" >&2 + _cmd="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 + if [ $(wbinfo -u|wc -l) != 0 ]; then + success=true + else + echo "ERROR: return user list from AD is empty" >&2 + success=false + fi + + # get rid of cred cache + kdestroy -c $KRB5CCNAME &> /dev/null +fi + +[[ $success == "false" ]] && exit 1 + +exit 0 |