Nss_Ldap ,AD, GSSAPI i zagnieżdżone grupy#

Przez dłuższy okres czasu na Linuxach używalismy winbinda do pozyskiwania informacji w linuxach o grupach i użytkownikach. Później przeszliśmy na nss_ldap, ale niestety nss_ldap nie rozwiązywał grup zagnieżdżonych, a dodatkowo wywalał się, gdy w grupie było duzo kont (tzn. więcej niz 1000), a niektóre grupy liczyły powyżej 3000 wpisów. Na szczęście w wersji 2.5.3 w końcu zadziałał parametr --with-ngroups i w końcu zadziałały grupy zagnieżdżone.

Problemem niestety dalej pozostaje nieobecność primaryGroup - tzn. grupa istnieje, ale użytkownicy nie są do niej przypisywani. Wszyscy użytkownicy w domenie, którą mają przypisaną jako primary group Domain Users. Listy dyskusyjne są tworzone z grup AD, więc jak ktoś chciał wysłać wiadomość do wszystkich, to niestety nie miał jak tego zrobić. Rozwiązaniem okazało się utworzenie nowej grupy dystrybucyjnej i dodanie do niej wszystkich głównych grup w domenie tj. studenci, pracownicy.

Innym problemem była konieczność wpisywaniana sztywno nazwy użytkownika i hasła potrzebnego do bindowania do AD w konfigu z możliwością odczytu przez każdego zalogowanego użytkownika. Udało się nam to obejść poprzez uruchomienie nscd, korzystanie z wpisu rootbinddn i ukrytego przed użytkownikami pliku ldap.secret z zapisanym hasłem. Takie rozwiązanie jednak nie pozwala użytkownikowi korzystać z poleceń typu finger lub id, przekazuje bowiem użytkownikowi tylko informację o uid-ach i gui-dach. Jako że użytkownicy są uwierzytelniani za pomocą pam_krb, po zalogowaniu mają bilet kerberosowy i mogą korzystać z SASLa z mechanizmem GSSAPI, a nss_ldap obsługuje te mechanizmy.

Poniżej parę zrzutów z działających konfiguracji:

przełączniki kompilacji do wersji 2.5.3
./configure --enable-rfc2307bis --enable-paged-results --with-ngroups=4000 --enable-mssfu-schema

plik ldap.conf
host lsd.pjwstk.edu.pl
base dc=pjwstk,dc=edu,dc=pl
rootbinddn XXXXXXXXX@pjwstk.edu.pl

use_sasl on
SASL_MECH GSSAPI
SASL_SECPROPS maxssf=0

nss_schema rfc2307bis
scope sub

nss_base_passwd ou=pracownicy,dc=pjwstk,dc=edu,dc=pl?sub
nss_base_passwd ou=studenci,dc=pjwstk,dc=edu,dc=pl?sub

nss_base_shadow ou=pracownicy,dc=pjwstk,dc=edu,dc=pl?sub
nss_base_shadow ou=studenci,dc=pjwstk,dc=edu,dc=pl?sub

nss_base_group ou=pracownicy,dc=pjwstk,dc=edu,dc=pl?sub
nss_base_group ou=studenci,dc=pjwstk,dc=edu,dc=pl?sub
nss_base_group cn=Users,dc=pjwstk,dc=edu,dc=pl?one

nss_map_objectclass posixAccount user
nss_map_objectclass shadowAccount user
nss_map_objectclass posixGroup Group

nss_map_attribute uid sAMAccountName
nss_map_attribute loginShell msSFU30LoginShell
nss_map_attribute uidNumber msSFU30UidNumber
nss_map_attribute gidNumber msSFU30GidNumber
nss_map_attribute homeDirectory msSFU30HomeDirectory
nss_map_attribute shadowLastChange pwdLastSet
nss_map_attribute gecos displayName
nss_map_attribute uniqueMember member
nss_map_attribute cn cn

pagesize 4000

nss_reconnect_tries 3
nss_reconnect_sleeptime 2
nss_reconnect_maxsleeptime 5
nss_reconnect_maxconntries 10


plik hosts
10.1.1.1 lsd.pjwstk.edu.pl. ldap.pjwstk.edu.pl pjwstk.edu.pl heroina.pjwstk.edu.pl kokaina.pjwstk.edu.pl thc.pjwstk.edu.pl mdma.pjwstk.edu.pl
10.1.1.1 thc pjwstk kokaina heroina mdma lsd ldap
10.1.1.1 forestDNSzones.pjwstk.edu.pl domainDNSzones.pjwstk.edu.pl
10.1.1.1 forestDNSzones domainDNSzones


plik krb5
[libdefaults]
default_realm = PJWSTK.EDU.PL
kdc_timesync = 1
ccache_type = 4
forwardable = true
proxiable = true

[realms]
PJWSTK.EDU.PL = {
kdc = 10.1.1.10
kdc = 10.1.1.1
kdc = 10.1.1.2
admin_server = 10.1.1.10
}

[domain_realm]
.pjwstk.edu.pl = PJWSTK.EDU.PL
pjwstk.edu.pl = PJWSTK.EDU.PL

[login]
krb4_convert = true
krb4_get_tickets = true


potrzbene paczki w debianie
do kompilacji: gcc g++ make
inne: libldap2 libldap2-dev libkrb5-dev

Thursday, October 26, 2006 2:17:28 PM (Central European Standard Time, UTC+01:00) #    Comments [0]  |  Trackback

 

Keytaby dla Linuxów#

Po długiej walce z keytabami w końcu okazało się, że narzędzie do tworzenia keytabów, czyli ktpass w W2k3SP1 po prostu nie działa. Na szczęście firma css-security napisala program (css_adkadmin), który może być bezpośrednio wykorzystywany z poziomu Linuxa i który radzi sobie z generacją kont w AD dla hostów i usług linuxowych, a także z wyciąganiem keytabów. Co ciekawe, Microsoft w dokumencie "Windows Security and Directory Services Guide for Unix v1.0" w ogóle nie wspomina o ktpass, polecając tylko css_adkadmin.

Korzystanie z programu jest w miarę miłe, trzeba mieć tylko wcześniej skonfigurowanego kerberosa i mieć ticket na użytkownika, który ma uprawnienia do zakładania kont w AD (ew. można jawnie podać nazwę użytkownika i hasło). Jedynym zgrzytem jest to, że keytaby są domyślnie generowane z encrypcją RC4 (która jest domyślna dla W2k3) zamiast DES-a (domyślnego dla W2k i wcześniejszych i kompatybilnego z Linuxami), co wymusza stosowanie flagi +desonly w opcji ank.

BSS | Linux
Thursday, October 26, 2006 1:49:04 PM (Central European Standard Time, UTC+01:00) #    Comments [0]  |  Trackback

 

Poprawianie DisplayName#

Po przejrzeniu kilku rekordów userów okazało się, że niektórzy mają w Display Name poustawiane pomiędzy imieniem i nazwiskiem 2 spacje zamiast 1. Aby to skorygować, powstał ten skrypt:

$data=get-date -format g
$comment="[changedDN "+$data+"by PeKi]"
$logname = "changedDN";
$logname += get-date -format "yyyyMMdd_hhmmss";
$logname +=".log";
$l=0
$Root = New-Object DirectoryServices.DirectoryEntry $strROOT
Searcher = New-Object DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $root
$searcher.Filter = "(&(objectClass=person)(objectcategory=person)(displayName=* *))";
$users = $searcher.findAll();
$log=@();
foreach ($user in $users)
{
    $de = New-Object DirectoryServices.DirectoryEntry $user.Path;
    $log+=$de.SAMAccountName + " " + $de.displayName
    $de.displayName=([String]$de.displayName).Replace(" "," ")
    $l++
}

echo "Przetworzonych " $l | out-file $logname -Append
$log| out-file $logname -Append

echo "Przetworzonych " $l
$log

A ten skrypt poprawia błąd, w którym nazwisko jest powtórzone 2 razy:

$data=get-date -format g
$comment="[changedDN "+$data+"by PeKi]"
$logname = "changedDNGN";
$logname += get-date -format "yyyyMMdd_hhmmss";
$logname +=".log";

$Root = New-Object DirectoryServices.DirectoryEntry $strROOT
Searcher = New-Object DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $root
$searcher.Filter = "(&(objectClass=person)(objectcategory=person)(displayname=* * *)(givenName=* *)(sn=*))";
$users = $searcher.findAll();
$log=@();
$l=0
foreach ($user in $users)
{
    $de = New-Object DirectoryServices.DirectoryEntry $user.Path;
    $gns=([String]$de.givenName).Split(' ')[1].tolower()
    $sn=([String]$de.sn).tolower()
    if ($gns.CompareTo($sn) -eq 0)
    {
    $newGN=([String]$de.givenName).Split(' ')[0];
    $newDN=$newGN+" "+([String]$de.sn)
    $log+=$de.SAMAccountName+ $de.givenName + $de.displayName + $newGN +$newDN
    write-host $l
    $de.givenName=$newGN
    $de.displayName=$newDN
    $l++
    }    
}

echo "Przetworzonych " $l | out-file $logname -Append
$log| out-file $logname -Append

echo "Przetworzonych " $l
$log

Thursday, October 26, 2006 1:30:43 PM (Central European Standard Time, UTC+01:00) #    Comments [0]  |  Trackback

 

Logowanie do AD z Grupami#

Po małym przeszukaniu internetu (głównieCodeProject i ExpertExchange) udało mi się w końcu zrobić logowanie do AD z wykorzystaniem grup (ale tylko tych jawnie wpisanych użytkowników, bez rozwijania grup zagnieżdżonych).

Główny kod programu:

//try autchenitcation
string[] groupsToCheck ={ "cwiczeniowcy", "Asystenci", "operatorzy", "wykladowcy" };

try
{
 //bind to ad
 DirectoryEntry de = new DirectoryEntry("LDAP://dc=pjwstk,dc=edu,dc=pl", TBLogin.Text,  TBHaslo.Text);
 DirectorySearcher mySearcher = new DirectorySearcher(de);
 mySearcher.Filter = ("(&(ObjectCategory=Person)(ObjectClass=user)(SAMAccountName=" + TBLogin.Text  + "))");
 mySearcher.PropertiesToLoad.Add("givenName");
 mySearcher.PropertiesToLoad.Add("sn");
 mySearcher.PropertiesToLoad.Add("Path");
 mySearcher.PropertiesToLoad.Add("primaryGroupId");
 mySearcher.PropertiesToLoad.Add("objectSid");
 SearchResult results = mySearcher.FindOne();
 if (null != results)
 {
  uImie = results.Properties["givenName"][0].ToString();
  uNazwisko = results.Properties["sn"][0].ToString();
  //get user groups
  ArrayList groups = new ArrayList();
  DirectoryEntry obUser = new DirectoryEntry(results.Path);
  object obGroups = obUser.Invoke("Groups");
  foreach (object ob in (IEnumerable)obGroups)
   {
    DirectoryEntry obGpEntry = new DirectoryEntry(ob);
    groups.Add(obGpEntry.Name);
   }
  //get primary group
  int primaryGroupId = (int)results.Properties["PrimaryGroupID"][0];
  byte[] userSid = results.Properties["objectSid"][0] as byte[];
  byte[] primaryGroupSid = BuildPrimaryGroupSID(userSid, primaryGroupId);
  string adsPath = String.Format("LDAP://<SID={0}>", BuildOctetString(primaryGroupSid));
  DirectoryEntry objUser = new DirectoryEntry(adsPath, TBLogin.Text, TBHaslo.Text);
  groups.Add((string)objUser.Properties["name"].Value);
  //check if member is in group
  bool isInGroup =false;
  foreach (string stc in groupsToCheck)
   if (groups.Contains(stc)) isInGroup = true;
  if (isInGroup)
   {
    //Authenticated
   }
  else
  {
   LblError.Text = "Nie masz uprawnien do wypelnienienia ankiety";
  }
 }
  else LblError.Text += "Blad Autoryzacji";
 }
catch (Exception dsce)
{
 LblError.Text = "Blad Autoryzacji";
}
// insertXML(uImie,uNazwisko);

Do tego potrzebujemy jeszcze funkcję do konwersji SIDów

private byte[] BuildPrimaryGroupSID(byte[] userSid, int primaryGroupId)
{
 byte[] rid = BitConverter.GetBytes(primaryGroupId);
 for (int i = 0; i < rid.Length; i++)
 {
  userSid.SetValue(rid[i], new long[] { userSid.Length - (rid.Length - i) });
 }
 return userSid;
}

private string BuildOctetString(byte[] bytes)
{
 StringBuilder sb = new StringBuilder();
 for (int i = 0; i < bytes.Length; i++)
 {
  sb.AppendFormat("{0}", bytes[i].ToString("X2"));
 }
 return sb.ToString();
}

I klasę, która produkuje SID

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Runtime.InteropServices;
using PSID = System.IntPtr;
using HLOCAL = System.IntPtr;
using BOOL = System.Int32;
using HANDLE = System.IntPtr;
using DWORD = System.UInt32;

/// <summary>
/// Summary description for Class1
/// </summary>
public class sid
{
 [DllImport("Advapi32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true,  CharSet = CharSet.Auto)]
 public static extern BOOL ConvertSidToStringSid(PSID Sid, out IntPtr StringSid);

 public unsafe static string UnsafeGetSidString(object sid)
 {
 string sidString = null;
 IntPtr strPtr;
 fixed (byte* psid = (byte[])sid)
  {
   IntPtr psidPtr = (IntPtr)psid;
   BOOL rc = ConvertSidToStringSid(psidPtr, out strPtr);
   Win32.CheckCall(rc);
   try
   {
    sidString = Marshal.PtrToStringAuto(strPtr);
   }
   finally
   {
    Win32.LocalFree(strPtr);
   }
  }
  return sidString;
 }
}
public class Win32
{
 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 public static extern HLOCAL LocalFree(HLOCAL hMem);
 public static void CheckCall(bool funcResult)
 {
  if (!funcResult)
  {
   ThrowLastError();
  }
  }
 public static void CheckCall(BOOL funcResult)
 {
  CheckCall(funcResult != 0);
 }
 public static void CheckCall(HANDLE funcResult)
 {
  CheckCall(!IsNullHandle(funcResult));
 }
 public static DWORD GetLastError()
 {
  return (DWORD)Marshal.GetLastWin32Error();
 }
 public static bool IsNullHandle(HANDLE ptr)
 {
  return (ptr == IntPtr.Zero);
 }
 public static void ThrowLastError()
 {
  Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
 }
}

 

Oczywiście w web.config w <system.web> musimy dodać obsługę funkcji typu unsafe:

        <compilation>
            <assemblies>
             <add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
            </assemblies>
            <compilers>
                <compiler language="c#;cs;csharp" extension=".cs" compilerOptions="/unsafe" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
            </compilers>
        </compilation>

Tuesday, October 17, 2006 9:33:34 AM (Central European Standard Time, UTC+01:00) #    Comments [2]  |  Trackback

 

All content © 2010, Krzysztof Pietrzak
On this page
This site
Calendar
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Archives
Sitemap
Blogroll OPML
  Tokyo by night
blog WiTa
  W-Files
blog n€x¤Ra
 .:fotoblog:.
blog Kfaza
 \\archon\blog$
blog archona
 Czasowstrzymywacz
Blog Fookyego
 Jog Pstryka
Jog Pstryka
 Mac OS X vs. Active Directory
techniczny blog kfaza

Maps
Locations of visitors to this page