dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.95k stars 4.65k forks source link

LdapSessionOptions.VerifyServerCertificate is not supported in non-Windows and error message is not helpful. #60972

Open macsux opened 2 years ago

macsux commented 2 years ago

Description

LdapConnection fails to bind on Linux when running .NET 6.0.0-rc.2.21480.5 version of System.DirectoryServices.Protocols package and throws

Unhandled exception. System.DirectoryServices.Protocols.LdapException: The feature is not supported. 
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind()

The same code works when using switching to version 5.0.0 of System.DirectoryServices.Protocols or running under windows

Reproduction Steps

Run the following code under Linux

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
    <ItemGroup>
      <PackageReference Include="System.DirectoryServices.Protocols" Version="6.0.0-rc.2.21480.5" />
    </ItemGroup>
</Project>
using System.DirectoryServices.Protocols;
using System.Net;

var di = new LdapDirectoryIdentifier(server: "ad.almirex.com", 389);
var connection = new LdapConnection(di, new NetworkCredential("username","password","almirex.dc"));
connection.Bind();
Console.WriteLine("Hello, World!");

If the package version is switched to 5.0.0, the above code works.

Expected behavior

The code works

Actual behavior

The code throws

Regression?

This worked in .NET 5 version of the package.

Known Workarounds

None

Configuration

Tested with .NET 6.0.100-rc.2.21505.57 SDK on WSL2 Ubuntu

Other information

No response

ghost commented 2 years ago

Tagging subscribers to this area: @jay98014 See info in area-owners.md if you want to be subscribed.

Issue Details
### Description `LdapConnection` fails to bind on Linux when running .NET `6.0.0-rc.2.21480.5` version of `System.DirectoryServices.Protocols` package and throws ``` Unhandled exception. System.DirectoryServices.Protocols.LdapException: The feature is not supported. at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential) at System.DirectoryServices.Protocols.LdapConnection.Bind() ``` The same code works when using switching to version 5.0.0 of `System.DirectoryServices.Protocols` or running under windows ### Reproduction Steps Run the following code under Linux ```xml Exe net6.0 enable enable ``` ```csharp using System.DirectoryServices.Protocols; using System.Net; var di = new LdapDirectoryIdentifier(server: "ad.almirex.com", 389); var connection = new LdapConnection(di, new NetworkCredential("username","password","ad.almirex.dc")); connection.Bind(); Console.WriteLine("Hello, World!"); ``` If the package version is switched to `5.0.0`, the above code works. ### Expected behavior The code works ### Actual behavior The code throws ### Regression? This worked in .NET 5 version of the package. ### Known Workarounds None ### Configuration Tested with .NET 6.0.100-rc.2.21505.57 SDK on WSL2 Ubuntu ### Other information _No response_
Author: macsux
Assignees: -
Labels: `area-System.DirectoryServices`, `untriaged`
Milestone: -
macsux commented 2 years ago

I think this is related to Ntlm somehow being broken on .NET 6. I switched it up like this:

using System.DirectoryServices.Protocols;
using System.Net;

var di = new LdapDirectoryIdentifier(server: "ad.almirex.com", 389);
var connection = new LdapConnection(di, new NetworkCredential("user","password", "almirex.dc"), AuthType.Ntlm);
connection.Bind();
Console.WriteLine("Hello, World!");

and getting

Unhandled exception. System.DirectoryServices.Protocols.LdapException: An unknown authentication error occurred.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind()

The same code works with 5.0.0 package, and v6 version of package works on Windows.

joperezr commented 2 years ago

Thanks for logging the issue. Can you please try setting the connection auth type to Basic as well as setting the protocol version to 3.0?

var di = new LdapDirectoryIdentifier(server: "ad.almirex.com", 389);
var connection = new LdapConnection(di, new NetworkCredential("user", "password", "almirex.dc"));
connection.SessionOptions.ProtocolVersion = 3;
connection.AuthType = AuthType.Basic;
connection.Bind();
Console.WriteLine("Hello, World!");
macsux commented 2 years ago

Ok, that seems to have partially fixed the issue, I can connect to the LDAP endpoint on 389. It however fails to connect to LDAPS when running under linux, even though .NET 6 release notes says this is supposed to be supported. This is the code used:

using System.DirectoryServices.Protocols;
using System.Net;

var useSsl = true;
var port = useSsl ? 636 : 389;
var di = new LdapDirectoryIdentifier(server: "ad.almirex.com", port);
var connection = new LdapConnection(di, new NetworkCredential("username","password"), AuthType.Basic);
if (useSsl)
{
    connection.SessionOptions.SecureSocketLayer = true;
    connection.SessionOptions.VerifyServerCertificate = (ldapConnection, certificate) => true;
}
connection.Bind();
Console.WriteLine("Hello, World!");
joperezr commented 2 years ago

I believe ldaps is only supported when using protocol version 3. Can you try setting the protocol version to 3.0 in your connection before calling bind?

macsux commented 2 years ago

Didn't help. Looking closer it's actually choking on the setter for this line when run under Linux, but works fine on windows.

connection.SessionOptions.VerifyServerCertificate = (ldapConnection, certificate) => true;
Unhandled exception. System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
   at System.DirectoryServices.Protocols.LdapSessionOptions.set_VerifyServerCertificate(VerifyServerCertificateCallback value)
   at Program.<Main>$(String[] args) in /mnt/c/projects/ldapplay/Program.cs:line 12
joperezr commented 2 years ago

I see, that is definitely a bug in Linux that we should fix, I'll update the title of the issue to say that this specific session option is the one that isn't working correctly. Out of curiosity, is there a specific reason you are defining that callback? I ask because the SSL handshake will automatically verify the server certificate already, and seems like yours isn't really changing anything. Can we get confirmation that if you remove that line the code works as expected?

joperezr commented 2 years ago

Actually after looking this more closely, looks like the Option that this is internally trying to set is indeed not supported anywhere other than wldap32 (meaning this setting is not part of the LDAP spec) which explains why it doesn't work on Linux.

https://github.com/dotnet/runtime/blob/704f023147b49692cec842427b116e9ee7e64305/src/libraries/Common/src/Interop/Interop.Ldap.cs#L91

Perhaps what this issue should track is just to have the right documentation on this API to mention that this is not supported in Linux, and we should be throwing a better exception with better details on it.

macsux commented 2 years ago

We are trying to ignore ssl cert validation at least in lower tier environments, is this possible? The ability to ignore cert validation is a usual option in most TLS libraries.

On Mon., Nov. 8, 2021, 4:22 p.m. Jose Perez Rodriguez, < @.***> wrote:

Actually after looking this more closely, looks like the Option that this is internally trying to set is indeed not supported anywhere other than wldap32 (meaning this setting is not part of the LDAP spec) which explains why it doesn't work on Linux.

https://github.com/dotnet/runtime/blob/704f023147b49692cec842427b116e9ee7e64305/src/libraries/Common/src/Interop/Interop.Ldap.cs#L91

Perhaps what this issue should track is just to have the right documentation on this API to mention that this is not supported in Linux, and we should be throwing a better exception with better details on it.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/dotnet/runtime/issues/60972#issuecomment-963586802, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAINFWH7IEF2BQMLB6G5NALULA5SLANCNFSM5G5DSI3A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

macsux commented 2 years ago

Also, I'm getting a CA1416 warning on connection.SessionOptions.SecureSocketLayer = options.UseSsl; telling me this API is windows only. Is this a left over that forgot to be removed after LDAPS supported was added for Linux?

joperezr commented 2 years ago

Oh good catch, seems like when we added this feature we forgot to remove this attribute:

https://github.com/dotnet/runtime/blob/9c37cdc559a141c92e20e123c9a594fed3692fec/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs#L375

Would you be interested in submitting a PR fixing that? 😃

joperezr commented 2 years ago

We are trying to ignore ssl cert validation at least in lower tier environments, is this possible?

I'm not sure that is supported as the TLS handshaking happens at the openldap layer and we particularly want to make sure that this happens correctly, as any error during the handshaking process could result in openldap not encrypting the communication if we were to ignore the failure with the handshake.

macsux commented 2 years ago

I've submitted a PR to remove the leftover warning attribute.

I've found a workaround for this problem that works for me. By setting LDAPTLS_REQCERT=never environmental variable I can opt-out of SSL validation by OpenSSL as described by OpenSSL configuration. Interestingly trying to do this from within the process via Environment.SetEnvironmentVariable("LDAPTLS_REQCERT","never"); does not work.

The existing API VerifyServerCertificate offers a flexible callback that would allow users to provide a custom delegate on how cert validation should be performed. It seems that such callback feature is only available on Windows, as the only options that OpenSSL exposes are:

       TLS_REQCERT <level>
              Specifies what checks to perform on server certificates in a TLS
              session, if any. The <level> can be  specified  as  one  of  the
              following keywords:

              never  The   client   will  not  request  or  check  any  server
                     certificate.

              allow  The server certificate is requested. If no certificate is
                     provided,   the  session  proceeds  normally.  If  a  bad
                     certificate is provided,  it  will  be  ignored  and  the
                     session proceeds normally.

              try    The server certificate is requested. If no certificate is
                     provided,  the  session  proceeds  normally.  If  a   bad
                     certificate  is  provided,  the  session  is  immediately
                     terminated.

              demand | hard
                     These keywords are equivalent. The server certificate  is
                     requested.  If  no  certificate  is  provided,  or  a bad
                     certificate  is  provided,  the  session  is  immediately
                     terminated. This is the default setting.

While my workaround works for my use case, it's not very good long term because

  1. It has to be set outside of the process
  2. It affects ALL LDAP connections in the app.

I think it's worth discussing at this point whether a simplified SkipCertificateValidation or similarly named boolean property should be added to the LdapSessionOptions to give some degree of control over cert validation within code. While not as powerful as the callback offered by VerifyServerCertificate, 99% of the time developers are just trying to do a blanked bypass of cert validation without any custom logic.

I believe the only thing that would be required is to call ldap_set_option with LDAP_OPT_X_TLS_REQUIRE_CERT to one of LDAP_OPT_X_TLS_NEVER if the above property is true. More details here.

joperezr commented 2 years ago

Thanks for submitting the PR. I'd be interested in debugging why setting it through Environment.SetEnvironmentVariable didn't work as I would have expected that to work.

jborean93 commented 2 years ago

Interestingly trying to do this from within the process via Environment.SetEnvironmentVariable("LDAPTLS_REQCERT","never"); does not work.

This is most likely because .NET sets environment vars for a process in a separate layer to the actual process environment block. I came across this issue before when trying to set KRB5_TRACE=/dev/stdout (or any of the other GSSAPI env vars) to debug some stuff and the C layer was unable to see these env vars.

To set an env var at runtime for use by a called "PInvoke" function you need to essentially call setenv in libc like

using System;
using System.Runtime.InteropServices;

namespace Native
{
    public class Libc
    {
        [DllImport("libc")]
        public static extern void setenv(string name, string value);

        [DllImport("libc")]
        public static extern void unsetenv(string name);
    }
}

You would then do Native.Libc.setenv("LDAPTLS_REQCERT", "never"); to set the env var.

parrssee commented 2 years ago

@joperezr @macsux So, I still did not understand whether the SSL connection works on Linux or not. I have already searched all similar threads on this topic and could not find a normal solution. Here's code snippet that I took from @macsux to simply connect to my LDAP server over 636 port:

using System;
using System.DirectoryServices.Protocols;
using System.Net;

var di = new LdapDirectoryIdentifier(server: "<server>", 636);
using var connection = new LdapConnection(di, new NetworkCredential("LDAP\\user", "password"), AuthType.Basic);
connection.SessionOptions.ProtocolVersion = 3;
connection.SessionOptions.SecureSocketLayer = true;
Console.WriteLine("Hello, World!");

Am I missing something? I also tried to use connection.SessionOptions.VerifyServerCertificate += (ldapConnection, certificate) => true; but it doesn't help at all, and according to this thread this code doesn't work for Linux. I'm always get this kind of errors:

Unhandled exception. System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind()
   at <Program>$.<Main>$(String[] args) in Program.cs:line 10

It worth to mention that if I connect through 389 port everything is OK. Does this library currently support SSL connection on Linux? I'm quite stuck and would be very grateful for help.

macsux commented 2 years ago

It works but if you're looking to skip ssl validation you have to do it via env var rather then the callback event. See my earlier comments

parrssee commented 2 years ago

It works but if you're looking to skip ssl validation you have to do it via env var rather then the callback event. See my earlier comments

Quite the contrary, I don't need to skip this validation and because of this I don't know how to connect over 636 port

macsux commented 2 years ago

This code is confirmed to work https://github.com/NMica/NMica.Security/blob/3868ceab4880058fbca4b66d651a169d04757d1d/src/NMica.AspNetCore.Authentication.Spnego/Ldap/LdapRolesClaimsTransformation.cs#L204

On Thu., Mar. 17, 2022, 1:40 p.m. parrssee, @.***> wrote:

It works but if you're looking to skip ssl validation you have to do it via env var rather then the callback event. See my earlier comments

Quite the contrary, I don't need to skip this validation and because of this I don't know how to connect over 636 port

— Reply to this email directly, view it on GitHub https://github.com/dotnet/runtime/issues/60972#issuecomment-1071136088, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAINFWDKZ2X7GY7L4T3LU33VANU7RANCNFSM5G5DSI3A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you were mentioned.Message ID: @.***>

parrssee commented 2 years ago

@macsux, Thank You. I'll try this out

parrssee commented 2 years ago

@macsux I tried Your solution but got the same error as always:

Unhandled exception. System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind()
   at <Program>$.<Main>$(String[] args) in Program.cs:line 25

I use the same server address string as for Windows: var di = new LdapDirectoryIdentifier(server: "10.6.1.162", 636, true, false); Am I using wrong connection string? Maybe external connection to this IP address through 636 port is blocked, I'll try to contact my DevOps.

akamud commented 2 years ago

@macsux I tried Your solution but got the same error as always:

Unhandled exception. System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind()
   at <Program>$.<Main>$(String[] args) in Program.cs:line 25

I use the same server address string as for Windows: var di = new LdapDirectoryIdentifier(server: "10.6.1.162", 636, true, false); Am I using wrong connection string? Maybe external connection to this IP address through 636 port is blocked, I'll try to contact my DevOps.

Have you managed to solve this? I'm trying the same thing on a Mac, and nothing on this thread worked, I am still getting a "The LDAP server is unavailable" message when I bind. I'm connecting pretty much exactly like mentioned here.

This is the code right now:

        var ldapInfo = new LdapDirectoryIdentifier(config.Server, config.Port);
        var ldapCred =
            new NetworkCredential(ldapCred.User, ldapCred.Password, ldapCred.Domain);
        _ldapConnection = new LdapConnection(ldapInfo, ldapCred)
        {
            AuthType = AuthType.Basic,
            SessionOptions =
            {
                ReferralChasing = ReferralChasingOptions.None,
                ProtocolVersion = 3,
#pragma warning disable CA1416
                SecureSocketLayer = true
#pragma warning restore CA1416
            }
        };
parrssee commented 2 years ago

@akamud We've found out that this issue was related to the wrong configurated LDAP server & missing certificates. Also, I found that specifying FQDN as server address in the LdapDirectoryIdentifier not working on the Linux platform, probably because of this. Also, there's the VerifyServerCertificate callback in which you must validate your LDAP certificate, but this functionality not working on the Linux (because, on the Linux, we are basically only passing through the Bind to the underlying native library), and I'm not sure about Mac platform.

Here's the code sample of the class that I wrote to manage LDAP connections (I changed the code a bit so as not to violate the police company I work with):

string address = "some.ldap.server.com"; // You can specify port number here and then extract it from this string
int port = IsSsl ? 636 : 389;           
var directoryIdentifier = new LdapDirectoryIdentifier(address, port);
_ldapConnection = new LdapConnection(directoryIdentifier, credential, AuthType.Basic);
_ldapConnection.SessionOptions.ReferralChasing = _referralChasingOptions; // All
_ldapConnection.SessionOptions.ProtocolVersion = LDAP3ProtocolVersion; // 3
_ldapConnection.SessionOptions.SecureSocketLayer = IsSsl;
_ldapConnection.Timeout = Timeout;
_ldapConnection.AutoBind = AutoBind; 
// When using LDAPS in Linux we are basically only passing through the Bind to the underlying native library (openldap), which does perform a certificate validation.
// If the certificate fails to validate successfully, we will get an exception during call to Bind() which should have more info saying that the certificate was invalid or
// that the connection with the server couldn't be established.
// In case of Win NT systems we can use the built-in verification functionality provided in the X509Cerificate2 class
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
    _ldapConnection.SessionOptions.VerifyServerCertificate += 
        (ldapConnection, certificate) => new X509Certificate2(certificate).Verify();
}   

In the case of Mac, maybe you must verify the certificate yourself too (like on Win), so try to do some experiments with this sample.

akamud commented 2 years ago

@akamud We've found out that this issue was related to the wrong configurated LDAP server & missing certificates. Also, I found that specifying FQDN as server address in the LdapDirectoryIdentifier not working on the Linux platform, probably because of this. Also, there's the VerifyServerCertificate callback in which you must validate your LDAP certificate, but this functionality not working on the Linux (because, on the Linux, we are basically only passing through the Bind to the underlying native library), and I'm not sure about Mac platform.

Here's the code sample of the class that I wrote to manage LDAP connections (I changed the code a bit so as not to violate the police company I work with):

string address = "some.ldap.server.com"; // You can specify port number here and then extract it from this string
int port = IsSsl ? 636 : 389;           
var directoryIdentifier = new LdapDirectoryIdentifier(address, port);
_ldapConnection = new LdapConnection(directoryIdentifier, credential, AuthType.Basic);
_ldapConnection.SessionOptions.ReferralChasing = _referralChasingOptions; // All
_ldapConnection.SessionOptions.ProtocolVersion = LDAP3ProtocolVersion; // 3
_ldapConnection.SessionOptions.SecureSocketLayer = IsSsl;
_ldapConnection.Timeout = Timeout;
_ldapConnection.AutoBind = AutoBind; 
// When using LDAPS in Linux we are basically only passing through the Bind to the underlying native library (openldap), which does perform a certificate validation.
// If the certificate fails to validate successfully, we will get an exception during call to Bind() which should have more info saying that the certificate was invalid or
// that the connection with the server couldn't be established.
// In case of Win NT systems we can use the built-in verification functionality provided in the X509Cerificate2 class
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
    _ldapConnection.SessionOptions.VerifyServerCertificate += 
        (ldapConnection, certificate) => new X509Certificate2(certificate).Verify();
}   

In the case of Mac, maybe you must verify the certificate yourself too (like on Win), so try to do some experiments with this sample.

Thanks for the reply, in fact we found something similar here. One of our servers wasn't correctly configured, when we tried the other one it worked. Now we configured the server correctly and everything is working fine.

Regarding the VerifyServerCertificate, it doesn't work on macOS either, so I used the same strategy of setting the LDAPTLS_REQ env for the openldap verification.

@joperezr any chance the error messages from theses cases can be more helpful? I used Apache Directory Server to try and connect to the server using SSL and it provided me better error messages, only then we started to investigate our server. The "server is unavailable" message from the .NET implementation is very generic.

joperezr commented 2 years ago

Yes, we have several issues for trying to surface the error messages better, we can use this issue to track the work of making that better for .NET 7

erosen03 commented 2 years ago

@joperezr was LdapSessionOptions.VerifyServerCertificate ever implemented on Linux? I'm having the exact same issues as the other folks on this thread, and the only workaround was to issue export LDAPTLS_REQCERT=never in the parent process of pwsh (i.e. the bash shell that spawns PowerShell).

This is not a great workaround, because the export must be run prior to spawning the PowerShell process. It cannot be run from within a PowerShell script. This is very problematic when working with an AWS PowerShell Lambda Function.

jborean93 commented 2 years ago

You can set it in proc but you need to use PInvoke to call setenv in libc directly and not through the dotnet APIs. It’s not ideal but it at least doesn’t rely on the env var being set before the process starts.

joperezr commented 1 year ago

@joperezr was LdapSessionOptions.VerifyServerCertificate ever implemented on Linux? I'm having the exact same issues as the other folks on this thread, and the only workaround was to issue export LDAPTLS_REQCERT=never in the parent process of pwsh (i.e. the bash shell that spawns PowerShell).

We haven't been able to prioritize this issue to add the support in Linux yet, but we would definitely be open into taking a contribution that addresses this in case anyone is interested in helping. Otherwise, we will keep it in the backlog and hopefully be able to get to it in 8.0, but that would greatly depend on the work going to the rest of the libraries and their priorities.

GeertvanHorrik commented 1 year ago

Been struggling with this as well (LPAP via SSL on Linux). A few things that might be useful for others:

  1. It's important to set the environment variable before creating the connection. I did it after creating the connection (where I used to set the server certificate validation callback), but then the underlying library seems to have already read the state and won't do that again. So if you are using the workaround by setting the environment via the setenv, make sure to do it earlier if it's not working for you.

  2. As suggested here, I've tried doing this "per connection / instance" instead by using the handle with custom interop. It looks like it's a global (handle should be null (or IntPtr.Zero)) setting according to this post and cannot be set per instance.

The Novell.Directory.Ldap.NETStandard library that some have mentioned can do this per connection / instance, but it would require a rewrite of most of the LDAP / AD related code.

michiproep commented 1 year ago

This code is confirmed to work https://github.com/NMica/NMica.Security/blob/3868ceab4880058fbca4b66d651a169d04757d1d/src/NMica.AspNetCore.Authentication.Spnego/Ldap/LdapRolesClaimsTransformation.cs#L204 On Thu., Mar. 17, 2022, 1:40 p.m. parrssee, @.> wrote: It works but if you're looking to skip ssl validation you have to do it via env var rather then the callback event. See my earlier comments Quite the contrary, I don't need to skip this validation and because of this I don't know how to connect over 636 port — Reply to this email directly, view it on GitHub <#60972 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAINFWDKZ2X7GY7L4T3LU33VANU7RANCNFSM5G5DSI3A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub. You are receiving this because you were mentioned.Message ID: @.>

I cannot believe that this is working on Linux. Line 214 "connection.SessionOptions.SecureSocketLayer = options.UseSsl;" is not support on Linux. It throws an exception.

All in all, this is quite a mess. My code is fully working on windows but I cannot connect using port 636 on linux. If I use 389, I can connect, but I have to use the full distinguished name as username and all my other methods in my class fail (e.g. searching users, etc). Also, I'm missing a good example on how to setup a dockerfile which includes all dependencies which actually works. Any help would be aprreciated. btw, I'm using .net 7

GeertvanHorrik commented 1 year ago

On linux (simplified version):

// call invoke on setting environment variable if you need to disable ssl verification

var identifier = new LdapDirectoryIdentifier("your-host", 636);
var networkCredential = new NetworkCredential(username, password, domain);

var connection = new LdapConnection(identifier, networkCredential, AuthType.Basic)
{
    AutoBind = false,
};

connection.SessionOptions.ProtocolVersion = 3;
connection.SessionOptions.SecureSocketLayer = true;

connection.Bind();
michiproep commented 1 year ago

For setting securesocketlayer I get a "not supported on this platform" exception

GeertvanHorrik commented 1 year ago

Did you use the exact code provided (auto bind to false, etc)? It works fine here.

michiproep commented 1 year ago

I'll send a Screenshot tomorrow

michiproep commented 1 year ago

@GeertvanHorrik grafik

This is the corresponding docker file:

FROM mcr.microsoft.com/dotnet/aspnet:7.0-alpine AS base WORKDIR /app RUN apk update \ && apk add --upgrade libldap \ && ln -s libldap.so.2 /usr/lib/libldap-2.4.so.2 ENV LDAPTLS_REQCERT=never EXPOSE 80 EXPOSE 443

GeertvanHorrik commented 1 year ago

Just to make sure:

Are you sure you are loading the correct runtime assemblies (Linux), not the reference ones from the nuget package?

The only difference I noticed in your example code is I am using net 6 jammy (Ubuntu) but that doesn't explain the difference in behavior.

michiproep commented 1 year ago

no. I'm not sure. how can I check? and which ones are the correct ones?

michiproep commented 1 year ago

Ok, this is a very confusing issue. I did reference System.DirectoryServices --version 7.0.1 which actually includes the System.DirectoryServices.Protocols namespace. Now, I found that there is another package available: System.DirectoryServices.Protocols --version 7.0.0

This one seems to work! Thanks for the hint.

But still confused about these two packages...

wast commented 1 year ago

This is working for me

// when using this constructor with servers, it doesn't work, it throws System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable
// var ldapDirectoryIdentifier = new LdapDirectoryIdentifier(_ldapSettings.Servers, _ldapSettings.Port, false, false); 

var ldapDirectoryIdentifier = new LdapDirectoryIdentifier(_ldapSettings.Servers.First(), _ldapSettings.Port);
using var ldapConnection = new LdapConnection(ldapDirectoryIdentifier);
var credentials = new NetworkCredential(_ldapSettings.ServiceUsername, _ldapSettings.ServicePassword);
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.AuthType = AuthType.Basic;
ldapConnection.SessionOptions.ReferralChasing = ReferralChasingOptions.None;
ldapConnection.SessionOptions.SecureSocketLayer = true;

Also I added issuing and root certificates to certificates store in Dockerfile.

YuriiSaichuk commented 1 year ago

Hello everyone, I have the same problem as described in the title of the article. At first I was getting the error

System.DirectoryServices.Protocols.LdapException: The feature is not supported. at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential new Credential, Boolean needSetCredential)

But after adding additional parameters to the connection, the error changed to

The LDAP server is unavailable.; Trace: at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)

Here my code and config samples:

` LdapDirectoryIdentifier identifier = new LdapDirectoryIdentifier(Domain); NetworkCredential credential = new NetworkCredential(Login, Password); string filter = $"(&(objectCategory=person)(SAMAccountName={samName}))"; string[] attributesToReturn = { "SAMAccountName", "givenname", "sn", "mail", "userPassword" };

        SearchRequest searchRequest = new SearchRequest(SomeGroup, filter, SearchScope.Subtree, attributesToReturn);

        try
        {
            using (LdapConnection connection = new LdapConnection(identifier, credential))
            {
                connection.SessionOptions.ProtocolVersion = 3;
                connection.AuthType = AuthType.Basic;
                connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None;
                connection.SessionOptions.SecureSocketLayer = true;
                connection.Bind();

                SearchResponse searchResponse = connection.SendRequest(searchRequest) as SearchResponse;

                return searchResponse.Entries.Count > 0;
            }
        }

`

where SomeGroup e.g. OU=Group,DC=example,DC=com Login and Password - credentials for group image

and docker config sample image

I may have configured the docker incorrectly (did not install the ldap client) or something else.

The test version of the program works correctly on Windows Server 2016 under IIS inside the domain controller. The version with the docker works on a different subnet connected via a CISCO router. The ldap server is pinged from the docker image.

The domain parameter for the test version is example.com. For the docker version, I tried different things, including the ldap server IP "System.DirectoryServices.Protocols" "7.0.0" "System.DirectoryServices" "7.0.1"

michiproep commented 1 year ago

@YuriiSaichuk : The only thing which worked for me is completly removing System.DirectoryServices-Package and just keep the .protocols package.

jhughesjha commented 1 year ago

@joperezr We are using the LdapConnection with LDAPS on Linux and need a way to validate the server certificate is the expected one per call. I know the VerifyServerCertificate is not supported due to the ldap option that it tries to set. Is there another way to validate the server certificate? Or do you recommend moving to another package for this use case?

joperezr commented 1 year ago

IIRC, in Linux the certificate is always validated automatically, the problem is that this is done by openldap layer which LdapConnection PInvokes into, but as you noted there is no hook (that I'm aware of) into OpenLdap validation process in order to run custom code for additional validation. My best guess is that if that is what is desired here (running custom validation apart from the general validation of the server certificate) you'd need to look into open ldap's documentation and see if there is something you can configure there.

CenturySparkle commented 1 year ago

This is working for me

// when using this constructor with servers, it doesn't work, it throws System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable
// var ldapDirectoryIdentifier = new LdapDirectoryIdentifier(_ldapSettings.Servers, _ldapSettings.Port, false, false); 

var ldapDirectoryIdentifier = new LdapDirectoryIdentifier(_ldapSettings.Servers.First(), _ldapSettings.Port);
using var ldapConnection = new LdapConnection(ldapDirectoryIdentifier);
var credentials = new NetworkCredential(_ldapSettings.ServiceUsername, _ldapSettings.ServicePassword);
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.AuthType = AuthType.Basic;
ldapConnection.SessionOptions.ReferralChasing = ReferralChasingOptions.None;
ldapConnection.SessionOptions.SecureSocketLayer = true;

OMG! This saved my bacon. This is working for me under .Net 7 running under the Ubuntu 22.04 container. (I do have the certificates installed as well.) It was setting the connection SessionOptions that got it working for me.

Deepak260726 commented 10 months ago

LdapDirectoryIdentifier identifier = new LdapDirectoryIdentifier("ldap.example.com", 389); NetworkCredential networkCredential = new NetworkCredential(username, userLoginparameter.Password);

LdapConnection ldapConnection = new LdapConnection(identifier, networkCredential);

if (Environment.OSVersion.Platform == PlatformID.Win32NT) { ldapConnection.SessionOptions.VerifyServerCertificate = (ldapConnection, certificate) => true; } else if (!Environment.GetEnvironmentVariables().Contains("LDAPTLS_REQCERT")) { _logger.LogWarning("LDAPS certificate validation is disabled in config, but LDAPTLS_REQCERT environmental variable is not set. On non-Windows environments certificate validation must be disabled by setting environmental variable LDAPTLS_REQCERT to 'never'"); }

ldapConnection.Bind(networkCredential);

i have tried all your opinion still getting the errors in Linux server but not in windows

DanielHabenicht commented 8 months ago

This error was driving me crazy. Although it manifested slightly differently. I got the following error, which I think is slightly worse as it directs you into a completely different direction:

System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
at System.DirectoryServices.Protocols.LdapSessionOptions.set_VerifyServerCertificate(VerifyServerCertificateCallback value)

On net8.0 (8.0.101) with the configuration VerifyServerCertificate = (ldapConnection, certificate) => true (for illustration in development) on Docker mcr.microsoft.com/dotnet/aspnet:8.0 with installed libldap-2.4-2_2.4.49.

Workaround for me:

  1. Removing the VerifyServerCertificate Property.
  2. Adding LDAPTLS_REQCERT=never before starting the dotnet program in the docker container.
madetara commented 6 months ago

To anyone stumbling upon this: the issue with SSL/TLS on Linux still persists even when you explicitly disable certificate verification (which I advise you to not do)

I would suggest simply switching to Novell LDAP client: it works on Linux and has better API