]> gitweb.fluxo.info Git - puppet-samba.git/commitdiff
Add implementation join Samba server into Active Directory
authorLebedev Vadim <abraham1901@gmail.com>
Mon, 18 Mar 2013 14:55:58 +0000 (18:55 +0400)
committerAdam Jahn <ajjahn@gmail.com>
Wed, 20 Mar 2013 01:14:30 +0000 (21:14 -0400)
Conflicts:
manifests/server/share.pp

README.md
manifests/init.pp
manifests/server.pp
manifests/server/ads.pp [new file with mode: 0644]
manifests/server/share.pp
manifests/server/winbind.pp [new file with mode: 0644]
templates/configure_active_directory.erb [new file with mode: 0644]
templates/verify_active_directory.erb [new file with mode: 0644]

index 4a57fd7d5373186ee7327cc3525c1b0aa29cb341..6bb9a79fe39b483a2bf87217790f48df35d67bc5 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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
index c71ee1d500144f9ff0265338ef0f25fac075dd07..8a914a4c77fe43d7c0606983a442de9ed8520e0c 100644 (file)
@@ -1,3 +1,7 @@
 class samba {
   include samba::server
+
+  if samba::server::security == 'ads' {
+    include samba::server::ads
+  }
 }
\ No newline at end of file
index cc1a95110122694da21b1069720c6b016945c03b..9b6d3c0df7649d981a8649895c2877c1bbc3786c 100644 (file)
@@ -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 (file)
index 0000000..1f8e602
--- /dev/null
@@ -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'] ],
+  }
+}
index 7d308a0546ec878dbbcb6f78cf60b55d9ac76ea5..b4eb02f23b04ac7395ccab5a00998e808f78ac4a 100644 (file)
@@ -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 (file)
index 0000000..76136b9
--- /dev/null
@@ -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 (file)
index 0000000..35ba86f
--- /dev/null
@@ -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 (file)
index 0000000..5a2a506
--- /dev/null
@@ -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