-
Notifications
You must be signed in to change notification settings - Fork 335
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
Support auth_gssapi authentication. #577
Conversation
See https://mariadb.com/kb/en/library/authentication-plugin-gssapi/ for plugin details. Signed-off-by: Vladislav Vaintroub <wlad@mariadb.com>
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.
Thanks for the contribution!
Overall this looks great; I've added a few notes about improving the robustness of the code, mostly for high-concurrency situations.
I assume you've tested this locally (by setting GSSAPIUser
in config.json for the tests)? I think it should be possible to get it automatically tested as part of the Travis CI builds (for the mariadb
Docker image); I can take a look at that after this is merged.
Ideally , to support mutual authentication between client and server , service principal name should be an optional connection parameter.
Would GSS Service Principal Name
(GssServicePrincipalName
) be an appropriate connection string option? I see the command-line client uses gssapi-principal-name
; I don't know if there's any "prior art" in other connector libraries. I have no objection to your updating this PR to include that setting if you think it would be good to support in the initial implementation.
(Is the verification implementation simply ensuring that AuthGSSAPI.GetServicePrincipalName()
returns the same value as was specified in the connection string?)
m_writePayloadLength = 0; | ||
} | ||
|
||
public override void Write(byte[] buffer, int offset, int count) => WriteAsync(buffer, offset, count, m_cancellationToken).Wait(); |
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 think this can be .GetAwaiter().GetResult()
too.
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.
Fixed that too.
Signed-off-by: Vladislav Vaintroub <wlad@mariadb.com>
8f95568
to
d66e1a6
Compare
Yes, I tested it this way, but just from Windows. On Linux, when I tried to run SideBySide tests, it complained about missing .NET Frameworks, and I ended up testing manually with a tiny console application, that did a connect and disconnect. I believe the easiest way to automatically test is on Windows, at least for smoke tests, providing the server and client run on the same machine. If both server and client run under domain user accounts, it will test Kerberos, if the machine is standalone, it would use NTLM. I'm not very familiar with appveyor, but our Java Connector uses it, for test, including automated MSI install https://github.com/MariaDB/mariadb-connector-j/blob/master/appveyor.yml , so maybe it is an option, too. My own Kerberos setup is rather involved, my KDC is Windows Active Directory domain controller, with a Linux and Windows machines joined into the domain, within in a Virtual Box host-only subnet. (I wanted to test same platform or cross-plattform combinations, therefore I made it complicated). The single-machine Linux testing, with KDC, server and client on the same box is something I did not do this time. But it looks like other people have useful scripts for that , e.g https://github.com/dotnet/corefx/blob/master/src/System.Net.Security/tests/Scripts/Unix/setup-kdc.sh
I'd prefer ServerSPN (Server SPN) , because it is how SQLServer's ODBC and JDBC call it.
I thought of something else than comparing strings. A single service can have multiple SPNs, and currently some of them are better for some clients (I mentioned, currently Linux NegotiateStream does not support machine$@domain.com , but it could use host/machine@DOMAIN.COM instead) I also believe is that is Kerberos creators assumed that client knows the SPN without any help of the server. So what I would like to use instead, is - whenever ServerSPN is set, ignore whatever server has sent. Instead, use ServerSPN as targetName in AuthenticateAsClient. If the SPN is wrong, Negotiate might fallback to NTLM, on Windows anyway. We can check for fallback with NegotiateStream.IsMutuallyAuthenticated == false after AuthenticateAsClient, and throw an exception if this happens. This would render ServerSPN parameter unusable for standalone, non-domain-joined workstations, but I think it is fine if we document that ServerSPN parameter requires Kerberos. |
…name. This parameter is necessary to support mutual authentication feature of the Kerberos protocol, i.e client can verify server's identity with it. The parameter can not be used in NTLM scenarios. If Negotiate protocol falls back to NTLM, and ServerSPN is set, AuthenticationException will be thrown, because NTLM can not be used to verify server's identity. This ServerSPN can also be used in some cases to connect to server, if server does not report correct SPN to clients. Signed-off-by: Vladislav Vaintroub <wlad@mariadb.com>
Alright, I added the ServerSPN to the pull request, too. |
@@ -58,6 +58,7 @@ public void Defaults() | |||
#if !BASELINE | |||
Assert.Null(csb.ServerRsaPublicKeyFile); | |||
#endif | |||
Assert.Null(csb.ServerSPN); |
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.
These tests also run against Oracle's Connector/NET (to verify equivalent behaviour) so this line will need to be moved inside the #if !BASELINE
block to avoid a compiler error with MySqlData.
@@ -121,6 +122,7 @@ public void ParseConnectionString() | |||
"Port=1234;" + | |||
"protocol=pipe;" + | |||
"pwd=Pass1234;" + | |||
"server spn=mariadb/host.example.com@EXAMPLE.COM;" + |
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.
Move up to line 111 (to be inside #if !BASELINE
).
@@ -168,6 +170,7 @@ public void ParseConnectionString() | |||
Assert.False(csb.Pooling); | |||
Assert.Equal(1234u, csb.Port); | |||
Assert.Equal("db-server", csb.Server); | |||
Assert.Equal("mariadb/host.example.com@EXAMPLE.COM", csb.ServerSPN); |
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.
Move up to line 159 (to be inside #if !BASELINE
).
I see you fixed the failing appveyor in #579 . Thanks for explanation, now I see what #if BASELINE was for. |
Merged via #579. |
Thank you! |
Shipped in 0.47.0. |
This patch adds support for MariaDB's gssapi authentication plugin, passwordless authentication on Windows (in both domain and standalone machine environments), as well as on Linux, in Kerberos realm environment.
More information on authentication plugin https://mariadb.com/kb/en/library/authentication-plugin-gssapi
The patch uses .NET NegotiateStream to authenticate. It uses an auxiliary stream to convert NegotiateStream framing for SPNEGO/NTLM payloads into MySQL packet framing with the same payloads.
Things to note
Currently, the client "trusts" service principal name that server sends, and passes it as targetName in NegotiateStream.AuthenticateAsClientAsync. Ideally , to support mutual authentication between client and server , service principal name should be an optional connection parameter. If set, driver would ignore the string sent by the server. I did not add this into the patch, but, if we agree it is a good idea, I can be easily added later.
On Linux, NegotiateStream implementation currently only works correctly with service principle names (that's the dotnet implementation of it) . Most importantly , it does not understand UPNs user principle names , like machine$@domain.com, which are typically in use by MariaDB servers running inside Windows AD.
Thus by default, connection from a Linux client to Windows server would fail (.NET runtime turns machine$@domain.com into funny name machine$/domain.com, which KDC won't find).
There is however a simple workaround on the server side, to allow Linux client to connect Windows server, would be to set server parameter --gssapi-principal-name=HOST/fqdn@DOMAIN.COM (host SPN, usually added whenever machine joins the domain).