changeset 120:8d4b08714c90

Import existing uid/gid; document LDAP setup; no-downtime LDAP repopulation script
author Sylvain Beucler <beuc@beuc.net>
date Tue, 04 Aug 2009 01:14:34 +0200
parents a34e97e27050
children 4de601f0d69f
files doc/LDAP sbin/sv-fetch-passwd-ids sbin/sv-populate-ldap src/savane/backend/auth_ldif_export.py src/savane/svmain/models.py
diffstat 5 files changed, 202 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/doc/LDAP
+++ b/doc/LDAP
@@ -1,4 +1,102 @@
+Django
+======
+
 Backend:
 http://code.djangoproject.com/ticket/11526
 Synchro:
 http://www.djangosnippets.org/snippets/893/
+
+It looks like a truly synchronized LDAP Django auth backend is
+difficult. In a first step we'll just export the Django userbase to
+LDAP (instead of the other way around).
+
+In addition, Django's "sha1$" passwords are ridiculously incompatible
+with SSHA passwords used by LDAP (among others).  A solution is to
+implement a custom Django auth backend with support for SSHA
+passwords.
+
+
+OpenLDAP
+========
+
+# - domain: savannah.gnu.org
+# - organisation: (whatever)
+# - Allow LDAPv2 protocol: no
+# - HDB
+
+cat <<EOF | debconf-set-selections
+slapd slapd/no_configuration boolean false
+slapd slapd/domain string savannah.gnu.org
+slapd shared/organization string GNU
+slapd slapd/password2 password admin
+slapd slapd/password1 password admin
+EOF
+
+apt-get --assume-yes install slapd
+#dpkg-reconfigure slapd
+
+# Test:
+#ldapsearch -b 'dc=savannah,dc=gnu,dc=org' -D 'cn=admin,dc=savannah,dc=gnu,dc=org' -w admin -x
+
+# Alternatively: minimal config:
+cat <<EOF > /etc/ldap/slapd.conf:
+pidfile /var/run/slapd/slapd.pid
+modulepath /usr/lib/ldap
+moduleload back_bdb
+include /etc/ldap/schema/core.schema
+sizelimit unlimited
+
+index uid,uidNumber,gidNumber,memberUid,shadowExpire eq
+
+# DB n1
+database bdb
+directory /var/lib/ldap
+suffix "dc=savannah,dc=gnu,dc=org"
+rootdn "cn=admin,dc=savannah,dc=gnu,dc=org"
+rootpw admin
+
+access to attrs=userPassword,shadowLastChange
+        by dn="cn=admin,dc=gnu,dc=org" write
+	by anonymous auth
+        by self write
+        by * none
+EOF
+
+(in all case add the indexes)
+
+Unix auth
+=========
+
+# Enable user lookup with libnss-ldap.  For additional passwords
+# support you'll need libpam-ldap but we don't need it for Savane,
+# since we're using SSH keys instead of passwords.
+
+cat <<EOF | debconf-set-selections
+libnss-ldap shared/ldapns/ldap-server string ldap://127.0.0.1/
+libnss-ldap shared/ldapns/base-dn string dc=savannah,dc=gnu,dc=org
+libnss-ldap libnss-ldap/rootbinddn string cn=admin,dc=savannah,dc=gnu,dc=org
+libnss-ldap libnss-ldap/rootbindpw password admin
+libnss-ldap shared/ldapns/ldap_version select 3
+EOF
+
+apt-get --assume-yes install libnss-ldap
+cat <<EOF >> /etc/libnss-ldap.conf
+nss_base_passwd ou=users,dc=savannah,dc=gnu,dc=org
+nss_base_shadow ou=users,dc=savannah,dc=gnu,dc=org
+nss_base_group  ou=groups,dc=savannah,dc=gnu,dc=org
+EOF
+
+sed -i -e 's/^\(passwd:.*$\)/\1 ldap/' \
+       -e 's/^\(group:.*$\)/\1 ldap/'  \
+       -e 's/^\(shadow:.*$\)/\1 ldap/' \
+    /etc/nsswitch.conf
+
+# Fetching a user's groups is sadly pretty inefficient (e.g. try 'id
+# yourusername').  To compensate, you can install the Name Service
+# Caching Daemon:
+apt-get --assume-yes install nscd
+
+# To avoid user listings to be too long, you can either limit the
+# number of result entries in slapd (sizelimit), or filter out some
+# users, e.g. with nss_base_passwd
+# ou=users,dc=savannah,dc=gnu,dc=org?sub?!(shadowExpire=10)
new file mode 100644
--- /dev/null
+++ b/sbin/sv-fetch-passwd-ids
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Fetch user and group IDs for import in the Savane database
+# Copyright (C) 2009  Sylvain Beucler
+#
+# This file is part of Savane.
+# 
+# Savane is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# Savane is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This is meant to be called before disabling the old-school
+# replication crons and switching to LDAP authentication.
+
+(
+echo "CREATE TEMPORARY TABLE temp_table (username varchar(30), uidNumber int, gidNumber int);"
+echo "INSERT INTO temp_table VALUES "
+# ('admin', 65535), ('loic', 65536);
+cat /etc/passwd | while IFS=: read username pass uid gid rest; do
+  if [ $uid -gt 1000 ]; then
+    echo -n "('$username', $uid, $gid),";
+  fi;
+done | sed 's/,$//'
+echo ";"
+echo "UPDATE auth_user, svmain_extendeduser, temp_table
+  SET svmain_extendeduser.uidNumber = temp_table.uidNumber,
+      svmain_extendeduser.gidNumber = temp_table.gidNumber
+  WHERE auth_user.id = svmain_extendeduser.user_ptr_id
+    AND auth_user.username = temp_table.username;"
+) > t.sql
+echo "DROP TABLE temp_table;" >> t.sql
+
+(
+echo "CREATE TEMPORARY TABLE temp_table (name varchar(30), gidNumber int);"
+echo "INSERT INTO temp_table VALUES "
+# ('mifluz', 1004), ('figure', 1006);
+cat /etc/group | while IFS=: read name pass gid rest; do
+  if [ $gid -gt 1000 ]; then
+    echo -n "('$name', $gid),";
+  fi;
+done | sed 's/,$//'
+echo ";"
+echo "UPDATE auth_group, svmain_extendedgroup, temp_table
+  SET svmain_extendedgroup.gidNumber = temp_table.gidNumber
+  WHERE auth_group.id = svmain_extendedgroup.group_ptr_id
+    AND auth_group.name = temp_table.name;"
+) >> t.sql
+echo "DROP TABLE temp_table;" >> t.sql
--- a/sbin/sv-populate-ldap
+++ b/sbin/sv-populate-ldap
@@ -1,7 +1,49 @@
 #!/bin/bash
+# Populate slapd from Savane's LDIF export, with minimum downtime
+# Copyright (C) 2009  Sylvain Beucler
+#
+# This file is part of Savane.
+# 
+# Savane is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# Savane is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Tested on Debian >= Lenny
+. /etc/default/slapd
+
+# Create the new database in a different directory to reduce the slapd
+# downtime - better if /tmp is on the same filesystem than
+# /var/lib/ldap
+tmpdir=$(mktemp -d -p /var/lib/ldap)
+conf=$(mktemp)
+sed 's,^directory\s.*,directory "'$tmpdir'",' /etc/ldap/slapd.conf > $conf
+
+# Rebuild the LDAP database from scratch.  '-q' disables integrity
+# checks and is nearly 30x faster.  Still needs ~30s just for bare
+# slapadd - and more for the Python string substitutions and other
+# computings (the MySQL query itself requires only 1-2s).
+./sv auth_ldif_export | slapadd -f $conf -q
+rm -f $conf
+
+# Efficiently switch slapd database
 /etc/init.d/slapd stop
 rm -f /var/lib/ldap/*
-# '-q' disables integrity checks and is nearly 30x faster
-./sv auth_ldif_export | slapadd -q
-chown -R openldap: /var/lib/ldap/*
+mv $tmpdir/* /var/lib/ldap/
+chown -R $SLAPD_USER:$SLAPD_GROUP /var/lib/ldap/*
+rmdir $tmpdir
 /etc/init.d/slapd start
+
+# Invalidate nscd cache if used
+if [ which nscd > /dev/null ]; then
+    nscd --invalidate passwd
+    nscd --invalidate group
+fi
--- a/src/savane/backend/auth_ldif_export.py
+++ b/src/savane/backend/auth_ldif_export.py
@@ -28,6 +28,7 @@
 # - min gid: 1000
 # - default group: cn=svusers / gid=1000
 # - loginShell: /usr/local/bin/sv_membersh
+# - homedir: 2-level /home/u/us/username
 
 import sys
 import codecs
@@ -210,7 +211,7 @@
 
 # Dump groups
 group_saves = []
-svmain_models.ExtendedGroup.query_active_groups_raw(conn, ('group_id', 'name', 'gidNumber'))
+svmain_models.ExtendedGroup.query_active_groups_raw(conn, ('group_ptr_id', 'name', 'gidNumber'))
 res = conn.store_result()
 #for group in svmain_models.ExtendedGroup.objects.only('name'):
 for row in res.fetch_row(maxrows=0):
--- a/src/savane/svmain/models.py
+++ b/src/savane/svmain/models.py
@@ -533,7 +533,7 @@
     #forum_flags int(11) default NULL
 
     @staticmethod
-    def query_active_memberships_raw(conn):
+    def query_active_memberships_raw(conn, fields):
         """
         Return efficient query with all the users; used by LDIF export
         """