-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for TLS and connectionless LDAP connections on Linux #52904
Conversation
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
Show resolved
Hide resolved
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
Show resolved
Hide resolved
uris = $"{scheme}:{directoryIdentifier.PortNumber}"; | ||
} | ||
|
||
return LdapPal.SetStringOption(_ldapHandle, LdapOption.LDAP_OPT_URI, uris); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for directoryIdentifier.Servers
to, through bad input, contains only servers that are empty strings? If so uris will be null but it appears openldap treats that as return to default. Not sure whether that's what you want, or you want to avoid calling it in that case.
https://git.openldap.org/openldap/openldap/-/blob/master/libraries/libldap/options.c#L655
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is possible. As you said, OpenLDAP falls back to its default hostname (which can be configured in /etc/ldap/ldap.conf
) if the hostname is not set. I intentionally allowed that behavior here too since it would be expected if someone is familiar with OpenLDAP.
I think that's OK... though I don't know how the Windows side handles empty string hostnames. If someone wrote code on Linux, then tried to run on Windows, that could be an issue if Windows doesn't support default hostnames.
LdapDirectoryIdentifier directoryIdentifier = _directoryIdentifier as LdapDirectoryIdentifier; | ||
if (directoryIdentifier.Connectionless) | ||
{ | ||
scheme = "cldap://"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have any test for connectionless?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I wasn't sure how to test it. I think that since connectionless LDAP isn't standardized, it's only implemented in AD (or as far as I can tell, it's not implemented in slapd). I've only got access to one [production] instance of AD, so I didn't want to test there! :)
I imagine that this is currently broken in Linux since there are no provisions anywhere else to configure connectionless. I think that this patch should work for connectionless should work though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally we don't add features we can't test 🙂. But if the feature is simply "given this flag, use this scheme prefix" maybe it's reasonable to add if testing isn't straightforward.
@joperezr preferences?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that having a test would be ideally, but given the context here I think that if we can't come up with a good way to test this it may be reasonable.
src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
Outdated
Show resolved
Hide resolved
{ | ||
get => throw new PlatformNotSupportedException(); | ||
set => throw new PlatformNotSupportedException(); | ||
get => GetPtrValueHelper(LdapOption.LDAP_OPT_VERSION).ToInt32(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes a problem with setting the LDAP version on the connection by using IntPtr types rather than int directly.
Could you say more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my testing, I had a problem where the version was not being set properly when using an integer rather than an IntPtr, so it was defaulting to LDAPv2 on my machine and failing to connect. The API calls for an pointer to an int:
LDAP_OPT_PROTOCOL_VERSION
Sets/gets the protocol version. outvalue and invalue must be
int *.
I think this might work now because the value is being cast to an integer pointer on the C side, but from what I understand (which is very little) I think that this can fail on systems with different pointer sizes?
...ryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
Show resolved
Hide resolved
Thanks for the contribution @iinuwa we have not had much time to give this library a lot of attention. It is good to see the improvement. |
cc @blowdart who may be interested. |
...ectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
Outdated
Show resolved
Hide resolved
....DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs
Outdated
Show resolved
Hide resolved
16aec65
to
0fc56b9
Compare
I think the last 4 CI checks timed out; do I need to do anything about these? |
Let's try again and see whether it's fixed. |
@@ -77,8 +79,8 @@ static Ldap() | |||
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_initialize", CharSet = CharSet.Ansi, SetLastError = true)] | |||
public static extern int ldap_initialize(out IntPtr ld, string hostname); | |||
|
|||
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_init", CharSet = CharSet.Ansi, SetLastError = true)] | |||
public static extern IntPtr ldap_init(string hostName, int portNumber); | |||
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_create", CharSet = CharSet.Ansi, SetLastError = true)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you share the difference between ldap_init and ldap_create?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ldap_init
and ldap_initialize
both call ldap_create
internally. ldap_create
does the heavy lifting of allocating memory and instantiating global options and whatnot: ldap_init
and ldap_initialize
are just wrappers to easily specify the server using a hostname and port or an LDAP URI, respectively.
ldap_init
has been deprecated by the OpenLDAP developers for a few reasons, one of which is the inability to specify the protocol scheme using ldap_init
. Lbraries that are not compiled with #define LDAP_DEPRECATED 1
will not include ldap_init
. The OpenLDAP developers recommend using either ldap_create
or ldap_initialize
to replace ldap_init
.
FWIW, we use ldap_create
instead of ldap_initialize
when setting up an LdapConnection
since we don't know the value of the SessionOptions.SecureSocketLayer
property when we initialize the OpenLDAP handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks so much for the explanation. I agree with you that ldap_init
is deprecated and we shouldn't be using it any longer. The docs from openldap seem to only reference ldap_initialize
though, so I wonder if we should be using that instead of ldap_create
. What are your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind either way; it winds up doing the same thing.
What I was trying by using ldap_create
over ldap_initialize
to do was avoid setting the LDAP_OPT_URI
option twice (once during initialization, and again after the SessionOptions
is populated). But after reading through the code for ldap_initialize
again I've discovered we can accomplish that by passing a null
value as the URI. That would eliminate the need to call ldap_create
. I can do that in the morning if that's your preference.
The docs from openldap seem to only reference
ldap_initialize
(Reading through the mailing list, I think that the developers themselves admit that there's some stuff missing from the manpages. I don't know if ldap_create
's absence in the manpage is intentional or not.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I totally agree that the manpages are not complete, but I do feel a bit better on using the documented API over the undocumented one specially if there are no clear advantages over the other one. I’m not very familiar how they manage contributions, but most dev teams would be more hesitant to make a breaking change to documented APIs than to public but undocumented one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(even when I don’t really think that any of the two will ever be changed, but might as well be more careful)
....DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs
Show resolved
Hide resolved
Left some small comments but this looks great otherwise! Thanks so much for this great contribution @iinuwa I'm sure a lot of people will be happy with this 😄 |
FWIW I ran the scenario tests against an ActiveDirectory server from a linux client VM and they all ran just fine and were able to connect 🥳 My only comment left is regarding the use of |
In OpenLDAP, ldap_simple_bind_s is deprecated in favor of ldap_sasl_bind_s with the LDAP_SASL_SIMPLE auth method[1][]. Similarly, ldap_init is deprecated in favor of ldap_initialize[2][]. The newer APIs also allows us to specify a URI to use TLS with OpenLDAP. [1]: https://git.openldap.org/openldap/openldap/-/blob/OPENLDAP_REL_ENG_2_4_58/include/ldap.h#L1278 [2]: https://git.openldap.org/openldap/openldap//blob/OPENLDAP_REL_ENG_2_4_58/include/ldap.h#L1513
This commit manually specifies the LDAP URI option during connect (but before binding). This is necessary because in order to know the correct scheme, we need access to SessionOptions, which is not available until after initialization. Finally, it removes the PlatformUnsupportedException from the SessionOptions.SecureSocketLayer property. This makes it possible to use LDAP over TLS and connectionless (UDP) LDAP.
94aa32a
to
7b62eb0
Compare
Sorry for the delay. I made the change to use |
Hi, I'm having the problem with connecting to LDAP over SSL on Linux/Ubuntu and wondering if there's a fix for this today. |
@Windmiller75 if this is merged fairly soon it will be released in .NET 6 Preview 6 in a month or so. |
@joperezr I think this is ready to go. Two tests timed out and the other (Library release test on Linux x64) seems to have crashed: I don't think that it's related to the code though, since other Library tests on Linux passed. |
Thanks for making the change! Just a note for future contributions, it's ok if not all commits in your PR build individually, and usually it is good to not rebase in the middle so it is easier to track non-reviewed code vs already reviewed one unless there is a good reason to rebase. Thanks again for this great contribution, code looks good to me ,I have requeued the few CI runs that were failing due to unrelated issues and I'm locally double-checking against an Active Directory server to make sure all tests pass. After that I'll go ahead and merge 🎆 |
Tests against real server all passed as expected. Will merge as soon as CI is green |
Hello @joperezr! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
@iinuwa do you have interest or plans to look at anything else in this area? There's plenty of ideas, some of them likely small: |
@danmoseley Yeah, some of those look interesting. I'd look at the referrals and Kerberos issues on Linux, but I'd need an AD and KDC instance to test I think. (I'd have to do this on my free time, and I only have access to my personal Linux laptop, which doesn't have the resources/license to run a Windows VM.) Do you know of a free and easy way to get that set up? |
@iinuwa I am not aware of free Windows Server images for testing. (http://my.visualstudio.com/downloads I assume is for people with VS licenses). Perhaps there is a way using a free Azure trial but that's likely more than you want to deal with. |
May fix #43890.
This PR does a few things for LDAP support in Linux for System.DirectoryServices.Protocols:
ldap_init
andldap_simple_bind_s
withldap_create
andldap_sasl_bind_s
, respectively.IntPtr
types rather thanint
directly.The first two points are prerequisites for the ultimate goal, which is to add LDAP TLS support to Linux. Let me know if you want me to break this up into multiple PRs.
Testing
I tested this using by running the
osixia/openldap
image with a TLS listener on 1636, and extracted the self-signed CA to a file:I set the LDAP client CA certificate path in
/etc/ldap/ldap.conf
so OpenLDAP would trust the self-signed certificate:(I'm testing with OpenLDAP 2.10.12 on Ubuntu 20.04, in case paths for configs differ on different distributions.)
I also had to map the short container ID (e.g.
7fb28b4656af
) to127.0.0.1
in/etc/hosts
.Finally, I ran
env LDAP_TEST_SERVER_INDEX=4 dotnet test
to select the new test configuration I added inLdapConfiguration.xml
using TLS, and change the hostname to the short container ID in/etc/hosts
to make sure the client validated the server certificate. All tests passed successfully locally, and I could see the successful binds and operations in the container logs.