Есть ли способ заставить вызовы UserPrincipal.GetGroups() и UserPrincipal.GetAuthorizationGroups() использовать LDAPS (порт 636) вместо LDAP (порт 389)?

Мы готовимся к мартовскому обновлению Microsoft AD, чтобы разрешить безопасные вызовы только с использованием LDAPS, и, проверяя наш код .Net, я обнаружил, что вызовы UserPrincipal.GetGroups() и UserPrincipal.GetAuthorizationGroups() используют LDAP (порт 389), а не LDAPS (порт 636), даже если объект UserPrincipal был создан с PrincipalContext, установленным через LDAPS, например:

    // Explicitly using LDAPS (port 636)
    PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "our.corpdomain.com:636", "DC=our,DC=corpdomain,DC=com", ContextOptions.Negotiate);
    UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, "someuser");

    // These calls still use LDAP (port 389)
    var groups = userPrincipal.GetAuthorizationGroups();
    var groups2 = userPrincipal.GetGroups();

Кто-нибудь знает, почему это может произойти, и если да, то как заставить эти вызовы использовать LDAPS? Если их нельзя заставить, есть ли для них обходные пути?


person Jeffrey Smith    schedule 12.02.2020    source источник


Ответы (1)


Это, безусловно, ошибка в коде .NET, и я отвечу на ваш вопрос, но, как я уже упоминал в другом вашем вопросе, мартовское обновление не будет «разрешать только безопасные вызовы с использованием LDAPS». Обычный порт LDAP 389 будет работать после этого обновления. Я не видел никаких доказательств того, что они когда-либо планировали его отключить.

Но если вы хотите убедиться, что он никогда не использует порт 389, у вас не будет UserPrincipal. Используйте DirectoryEntry и/или DirectorySearcher напрямую, что UserPrincipal в любом случае использует в фоновом режиме. Это не первая обнаруженная мною ошибка в пространстве имен AccountManagement.

Я написал статью о поиск всех групп пользователя, в котором есть пример кода для разных сценариев. Вам придется изменить любой случай, когда вы создаете новый объект DirectoryEntry и указываете порт 636, например:

new DirectoryEntry("LDAP://example.com:636/CN=whatever,DC=example,DC=com")

На самом деле вы можете опустить доменное имя, если хотите (просто :636 вместо example.com:636).

Один случай, который я не рассмотрел в этой статье, эквивалентен GetAuthorizationGroups, то есть чтению атрибута tokenGroups. Это дает вам список идентификаторов SID групп, который вы затем можете найти, чтобы найти имя группы. Вот метод, который сделает это:

private static IEnumerable<string> GetTokenGroups(DirectoryEntry de) {
    var groupsFound = 0;

    //retrieve only the tokenGroups attribute from the user
    de.RefreshCache(new[] {"tokenGroups"});

    while (true) {
        var tokenGroups = de.Properties["tokenGroups"];
        foreach (byte[] groupSidByte in tokenGroups) {
            groupsFound++;
            var groupSid = new SecurityIdentifier(groupSidByte, 0);
            var groupDe = new DirectoryEntry($"LDAP://:{de.Options.PasswordPort}/<SID={groupSid}>");

            groupDe.RefreshCache(new[] {"cn"});
            yield return (string) groupDe.Properties["cn"].Value;
        }

        //AD only gives us 1000 or 1500 at a time (depending on the server version)
        //so if we've hit that, go see if there are more
        if (tokenGroups.Count != 1500 && tokenGroups.Count != 1000) break;

        try {
            de.RefreshCache(new[] {$"memberOf;range={groupsFound}-*"});
        } catch (COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) break; //no more results

            throw;
        }
    }
}

Это будет использовать любой порт, который вы использовали для создания объекта DirectoryEntry, который вы передаете. Однако это прерывается, если в вашей среде более одного домена. В этом случае все может осложниться, если вы хотите всегда использовать порт 636.

person Gabriel Luci    schedule 12.02.2020
comment
Спасибо, Габриэль! Я попробовал код, но он не находит столько групп, сколько GetAuthorizationGroups. Пытаюсь найти общий язык. Все дополнительные группы являются локальными встроенными группами («Все», «Администраторы», «Пользователи журнала производительности», «Прошедшие проверку», «Эта организация», «Утвержденное удостоверение безопасности» и «Высокий обязательный уровень»). Я читаю ваши статьи и не замечаю ничего, что могло бы быть виновником. У нас есть один домен и нет доверенных внешних доменов. Любые идеи? - person Jeffrey Smith; 13.02.2020
comment
Код, который я дал, вернет только группу AD. Упомянутые вами группы не являются группами AD. Они либо хорошо- известные SID (например, Everyone) или локальные группы. Каждая существующая учетная запись будет считаться частью Everyone. Локальные группы будут меняться в зависимости от компьютера, на котором вы запускаете этот код. Это то, что вам действительно нужно? - person Gabriel Luci; 13.02.2020
comment
Если вы хотите сделать это для текущего пользователя (пользователя, под которым выполняется код), вы можете вызвать WindowsIdentity.GetCurrent() и посмотрите на Groups. - person Gabriel Luci; 13.02.2020
comment
Странно то, что ваш код возвращает Users, который выглядит как хорошо известный SID (S-1-5-32-545). На самом деле, если вы посмотрите на этот MS link перечисляет каждый из них как группу, псевдоним, учетную запись или встроенную группу. Запуск whoami /groups показывает то же самое. Я не думаю, что нам нужны эти группы, но я не уверен. Код, который я устраняю, был создан кем-то другим, и я просто недостаточно знаю его или AD. - person Jeffrey Smith; 13.02.2020
comment
К сожалению, фрагмент кода, который я устраняю, не использует текущего пользователя, поэтому я не могу использовать WindowsIdentity.GetCurrent(). Кстати, мне нравятся ваши статьи. Они очень информативны! - person Jeffrey Smith; 13.02.2020
comment
В каждом домене есть группа Users с одинаковым SID, так что это нормально. Он находится в контейнере Builtin в корне домена (например, CN=Users,CN=Builtin,DC=example,DC=com). - person Gabriel Luci; 13.02.2020