Skip to content

Commit 2d708a9

Browse files
author
wlad
committed
auth_gssapi - optionally pass ServerSPN.
Signed-off-by: wlad <[email protected]>
1 parent d66e1a6 commit 2d708a9

File tree

9 files changed

+69
-4
lines changed

9 files changed

+69
-4
lines changed

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
7979
Keepalive = csb.Keepalive;
8080
PersistSecurityInfo = csb.PersistSecurityInfo;
8181
ServerRsaPublicKeyFile = csb.ServerRsaPublicKeyFile;
82+
ServerSPN = csb.ServerSPN;
8283
TreatTinyAsBoolean = csb.TreatTinyAsBoolean;
8384
UseAffectedRows = csb.UseAffectedRows;
8485
UseCompression = csb.UseCompression;
@@ -157,6 +158,7 @@ private static MySqlGuidFormat GetEffectiveGuidFormat(MySqlGuidFormat guidFormat
157158
public uint Keepalive { get; }
158159
public bool PersistSecurityInfo { get; }
159160
public string ServerRsaPublicKeyFile { get; }
161+
public string ServerSPN { get; }
160162
public bool TreatTinyAsBoolean { get; }
161163
public bool UseAffectedRows { get; }
162164
public bool UseCompression { get; }

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ private async Task<PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs,
501501
}
502502

503503
case "auth_gssapi_client":
504-
return await AuthGSSAPI.AuthenticateAsync(switchRequest.Data, this, ioBehavior, cancellationToken).ConfigureAwait(false);
504+
return await AuthGSSAPI.AuthenticateAsync(cs, switchRequest.Data, this, ioBehavior, cancellationToken).ConfigureAwait(false);
505505

506506
case "mysql_old_password":
507507
Log.Error("Session{0} is requesting AuthenticationMethod '{1}' which is not supported", m_logArguments);

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnectionStringBuilder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ public string ServerRsaPublicKeyFile
261261
set => MySqlConnectionStringOption.ServerRsaPublicKeyFile.SetValue(this, value);
262262
}
263263

264+
public string ServerSPN
265+
{
266+
get => MySqlConnectionStringOption.ServerSPN.GetValue(this);
267+
set => MySqlConnectionStringOption.ServerSPN.SetValue(this, value);
268+
}
269+
264270
public bool TreatTinyAsBoolean
265271
{
266272
get => MySqlConnectionStringOption.TreatTinyAsBoolean.GetValue(this);
@@ -371,6 +377,7 @@ internal abstract class MySqlConnectionStringOption
371377
public static readonly MySqlConnectionStringOption<bool> OldGuids;
372378
public static readonly MySqlConnectionStringOption<bool> PersistSecurityInfo;
373379
public static readonly MySqlConnectionStringOption<string> ServerRsaPublicKeyFile;
380+
public static readonly MySqlConnectionStringOption<string> ServerSPN;
374381
public static readonly MySqlConnectionStringOption<bool> TreatTinyAsBoolean;
375382
public static readonly MySqlConnectionStringOption<bool> UseAffectedRows;
376383
public static readonly MySqlConnectionStringOption<bool> UseCompression;
@@ -565,6 +572,10 @@ static MySqlConnectionStringOption()
565572
keys: new[] { "ServerRSAPublicKeyFile", "Server RSA Public Key File" },
566573
defaultValue: null));
567574

575+
AddOption(ServerSPN = new MySqlConnectionStringOption<string>(
576+
keys: new[] { "Server SPN", "ServerSPN" },
577+
defaultValue: null));
578+
568579
AddOption(TreatTinyAsBoolean = new MySqlConnectionStringOption<bool>(
569580
keys: new[] { "Treat Tiny As Boolean", "TreatTinyAsBoolean" },
570581
defaultValue: true));

src/MySqlConnector/Protocol/Serialization/AuthGSSAPI.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.IO;
33
using System.Net;
44
using System.Net.Security;
5+
using System.Security;
6+
using System.Security.Authentication;
57
using System.Text;
68
using System.Threading;
79
using System.Threading.Tasks;
@@ -216,13 +218,13 @@ private static string GetServicePrincipalName(byte[] switchRequest)
216218
var reader = new ByteArrayReader(switchRequest.AsSpan());
217219
return Encoding.UTF8.GetString(reader.ReadNullOrEofTerminatedByteString());
218220
}
219-
public static async Task<PayloadData> AuthenticateAsync(byte[] switchRequestPayloadData,
221+
public static async Task<PayloadData> AuthenticateAsync(ConnectionSettings cs, byte[] switchRequestPayloadData,
220222
ServerSession session, IOBehavior ioBehavior, CancellationToken cancellationToken)
221223
{
222224
using (var innerStream = new NegotiateToMySqlConverterStream(session, ioBehavior, cancellationToken))
223225
using (var negotiateStream = new NegotiateStream(innerStream))
224226
{
225-
var targetName = GetServicePrincipalName(switchRequestPayloadData);
227+
var targetName =cs.ServerSPN ?? GetServicePrincipalName(switchRequestPayloadData);
226228
#if NETSTANDARD1_3
227229
await negotiateStream.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, targetName).ConfigureAwait(false);
228230
#else
@@ -235,6 +237,13 @@ public static async Task<PayloadData> AuthenticateAsync(byte[] switchRequestPayl
235237
await negotiateStream.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, targetName).ConfigureAwait(false);
236238
}
237239
#endif
240+
if (cs.ServerSPN != null && !negotiateStream.IsMutuallyAuthenticated)
241+
{
242+
// Negotiate used NTLM fallback, server name cannot be verified.
243+
throw new AuthenticationException(String.Format(
244+
"GSSAPI : Unable to verify server principal name using authentication type {0}",
245+
negotiateStream.RemoteIdentity?.AuthenticationType));
246+
}
238247
if (innerStream.MySQLProtocolPayload.ArraySegment.Array != null)
239248
// return already pre-read OK packet.
240249
return innerStream.MySQLProtocolPayload;

tests/MySqlConnector.Tests/MySqlConnectionStringBuilderTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public void Defaults()
5858
#if !BASELINE
5959
Assert.Null(csb.ServerRsaPublicKeyFile);
6060
#endif
61+
Assert.Null(csb.ServerSPN);
6162
#if !BASELINE
6263
Assert.Equal(MySqlSslMode.Preferred, csb.SslMode);
6364
#else
@@ -121,6 +122,7 @@ public void ParseConnectionString()
121122
"Port=1234;" +
122123
"protocol=pipe;" +
123124
"pwd=Pass1234;" +
125+
"server spn=mariadb/[email protected];" +
124126
"Treat Tiny As Boolean=false;" +
125127
"ssl mode=verifyca;" +
126128
"Uid=username;" +
@@ -168,6 +170,7 @@ public void ParseConnectionString()
168170
Assert.False(csb.Pooling);
169171
Assert.Equal(1234u, csb.Port);
170172
Assert.Equal("db-server", csb.Server);
173+
Assert.Equal("mariadb/[email protected]", csb.ServerSPN);
171174
Assert.False(csb.TreatTinyAsBoolean);
172175
Assert.Equal(MySqlSslMode.VerifyCA, csb.SslMode);
173176
Assert.False(csb.UseAffectedRows);

tests/SideBySide/AppConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public static class AppConfig
3838

3939
public static string GSSAPIUser => Config.GetValue<string>("Data:GSSAPIUser");
4040

41+
public static bool HasKerberos => Config.GetValue<bool>("Data:HasKerberos");
42+
4143
public static string SecondaryDatabase => Config.GetValue<string>("Data:SecondaryDatabase");
4244

4345
private static ServerFeatures UnsupportedFeatures => (ServerFeatures) Enum.Parse(typeof(ServerFeatures), Config.GetValue<string>("Data:UnsupportedFeatures"));

tests/SideBySide/ConfigSettings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public enum ConfigSettings
1717
TcpConnection = 0x200,
1818
SecondaryDatabase = 0x400,
1919
KnownClientCertificate = 0x800,
20-
GSSAPIUser = 0x1000
20+
GSSAPIUser = 0x1000,
21+
HasKerberos = 0x2000
2122
}
2223
}

tests/SideBySide/ConnectAsync.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Data;
33
using System.Diagnostics;
44
using System.IO;
5+
using System.Security.Authentication;
56
using System.Threading.Tasks;
67
using MySql.Data.MySqlClient;
78
using Xunit;
@@ -320,6 +321,39 @@ public async Task AuthGSSAPI()
320321
await connection.OpenAsync();
321322
}
322323
}
324+
325+
[SkippableFact(ConfigSettings.GSSAPIUser | ConfigSettings.HasKerberos)]
326+
public async Task GoodServerSPN()
327+
{
328+
var csb = AppConfig.CreateGSSAPIConnectionStringBuilder();
329+
string serverSPN;
330+
// Use server's variable gssapi_principal_name as SPN
331+
using (var connection = new MySqlConnection(csb.ConnectionString))
332+
{
333+
await connection.OpenAsync();
334+
using (var cmd = connection.CreateCommand())
335+
{
336+
cmd.CommandText = "select @@gssapi_principal_name";
337+
serverSPN = (string) await cmd.ExecuteScalarAsync();
338+
}
339+
}
340+
csb.ServerSPN = serverSPN;
341+
using (var connection = new MySqlConnection(csb.ConnectionString))
342+
{
343+
await connection.OpenAsync();
344+
}
345+
}
346+
347+
[SkippableFact(ConfigSettings.GSSAPIUser)]
348+
public async Task BadServerSPN()
349+
{
350+
var csb = AppConfig.CreateGSSAPIConnectionStringBuilder();
351+
csb.ServerSPN = "BadServerSPN";
352+
using (var connection = new MySqlConnection(csb.ConnectionString))
353+
{
354+
await Assert.ThrowsAsync<AuthenticationException>(() => connection.OpenAsync());
355+
}
356+
}
323357
#if !BASELINE
324358
[Fact]
325359
public async Task PingNoConnection()

tests/SideBySide/TestUtilities.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public static string GetSkipReason(ServerFeatures serverFeatures, ConfigSettings
9494
if (configSettings.HasFlag(ConfigSettings.GSSAPIUser) && string.IsNullOrWhiteSpace(AppConfig.GSSAPIUser))
9595
return "Requires GSSAPIUser in config.json";
9696

97+
if (configSettings.HasFlag(ConfigSettings.HasKerberos) && !AppConfig.HasKerberos)
98+
return "Requires HasKerberos in config.json";
99+
97100
if (configSettings.HasFlag(ConfigSettings.CsvFile) && string.IsNullOrWhiteSpace(AppConfig.MySqlBulkLoaderCsvFile))
98101
return "Requires MySqlBulkLoaderCsvFile in config.json";
99102

0 commit comments

Comments
 (0)