-
Notifications
You must be signed in to change notification settings - Fork 286
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
Feature | Adding TDS 8 support #1608
Conversation
* Adding TDS8 support to netcore * Doc changes for TDS 8 Co-authored-by: JRahnama <v-jarahn@microsoft.com>
_sslOverTdsStream = new SslOverTdsStream(_tcpStream, _connectionId); | ||
stream = _sslOverTdsStream; | ||
} | ||
_sslStream = new SNISslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); |
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 ValidateServerCertificate
an instance method that we need to create a delegate for each time we create the connection? If it's static or takes a context parameter we could avoid the allocation.
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.
@Wraith2 thanks for looking into this, ValidateServerCertificate is not static and is following the pattern mentioned for RemoteCertificateValidationCallback Delegate. What do you suggest to avoid the allocation?
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.
If you could check the sender
argument in the callback and find if it's the SNISslStream
that'll tell you whether it could be used as the context object here. If it is then you don't need the instance and you can use a static callback.
This isn't really that important and it'll only happen once per connection so don't worry about it too much, I'd say it's more important to get everything working first.
SslClientAuthenticationOptions sslClientOptions = new() | ||
{ | ||
TargetHost = _serverIndicationName, | ||
ApplicationProtocols = new List<SslApplicationProtocol>() { new(TdsEnums.TDS8) }, |
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.
Does this need to be a per instance collection? Is if going to be modified by the caller? if it isn't going to be modified we could have a single static instance that we reuse. If it has to be instantiated consider using the ctor that specifies the initial size because the default initial size is 5 and if we know this will almost never grow we waste some memory each time.
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 check the new changes I made?
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
Outdated
Show resolved
Hide resolved
if (_isTDS8) | ||
{ | ||
#if NETCOREAPP | ||
SslApplicationProtocol TDS8 = new("tds/8.0"); |
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.
Nit: naming would be tds8
since it's a local not a public member. I think I'd probably just call it protocol
since tds is the value not the description of the variable that holds it.
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 would define a static list of client-supported protocols somewhere above (private static clientApplicationProtocols s_Protocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("tds/8.0") }
) that you can reference on line 333 and remove this local variable completely.
EDIT: I now see that Wraith already suggested the same thing in SNITcpHandle.cs. We can use the same static reference here and there.
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 check the new changes I made?
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 SNIPhysicalHandle
create a new field private static s_clientApplicationProtocols s_Protocols = new List<SslApplicationProtocol>(1) { new SslApplicationProtocol("tds/8.0") };
as David suggested and then in SNINpHandle
and SNITcpHandle
use it like this:
SslClientAuthenticationOptions sslClientOptions = new()
{
TargetHost = _serverNameIndication,
ApplicationProtocols = SNIPhysicalHandle.s_clientApplicationProtocols ,
EnabledSslProtocols = SupportedProtocols,
ClientCertificates = null,
};
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
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.
Looks ok in general.
Have you considered using an int protocol version instead of a bool? It seems sensible to think there may be later versions and make switching on the version cleaner.
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.
Not done reviewing, but wanted to submit some partial feedback.
As we discussed offline, we should also try to maintain a backwards compatible connection string, where possible. Specifically, with the new encrypt enum and Optional/Mandatory values, when we output a connection string, we should continue to output Encrypt=true
or Encrypt=false
for Mondatory/Optional respectively. This will preserve some backwards compatibility between versions in scenarios where an application is passing connection strings around. This is more important since Encrypt is such a common/well-used option.
doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptionOption.xml
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
if (_isTDS8) | ||
{ | ||
#if NETCOREAPP | ||
SslApplicationProtocol TDS8 = new("tds/8.0"); |
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 would define a static list of client-supported protocols somewhere above (private static clientApplicationProtocols s_Protocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("tds/8.0") }
) that you can reference on line 333 and remove this local variable completely.
EDIT: I now see that Wraith already suggested the same thing in SNITcpHandle.cs. We can use the same static reference here and there.
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: David Engel <dengel1012@gmail.com>
EnabledSslProtocols = SupportedProtocols, | ||
ClientCertificates = null, | ||
}; | ||
_sslStream.AuthenticateAsClientAsync(sslClientOptions).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.
This is using an async function and then awaiting it synchronously, if @roji sees you doing this you'll make him sad. See if there is a naturally synchronous AuthenticateAsClient
method you can use instead and avoid sync-over-async.
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.
there is no AuthenticateAsClient that accepts SslClientAuthenticationOption as an argument in netcore 3.1. It started from net5 and after.
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.
Netcore 3.1 goes out of support at the end of this year (it's LTS) and net5 is already unsupported. https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
You can use compiler ifdefs to use the current pattern only on 3.1 and lower and then use the sync overload from net5 onwards. In this case I think it's acceptable to do that because it will avoid more threading problems.
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.
Another partial review. In general, where we were previously passing bool encrypt
internally and we are now passing SqlConnectionEncryptionOption encrypt
plus bool isTDS8
, we could simplify the code by just passing bool encrypt
plus bool isTDS8
(or more accurately bool tlsFirst
). This would make for fewer code changes related to the change of the encrypt parameter type.
Alternatively, just pass SqlConnectionEncryptionOption encrypt
and base all the if (isTDS8)
logic off of if (encrypt == SqlConnectionEncryptionOption.Strict)
.
Since I haven't been all the way through the code, feel free to point to to why either of the above wouldn't work.
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Outdated
Show resolved
Hide resolved
Port additional code to netfx
Add HostNameInCertificate synonym to align with other drivers
doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml
Outdated
Show resolved
Hide resolved
doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml
Outdated
Show resolved
Hide resolved
doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
Update error message
Co-authored-by: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.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.
Generally, I believe there are some opportunities to add more event source logs, especially in the new paths that would be better if we could include them with this change.
src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
Show resolved
Hide resolved
Remove unused parameters
TDS8 support is in preview mode for SQL server 2022. This PR is to add client-side support.
Preview MS-TDS spec changes:
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-winprotlp/8a9c667b-2825-46a8-8066-a80681233c33
Behavior:
To use TDS 8, users should specify Encrypt=Strict in the connection string. Strict mode disables TrustServerCertificate (always treated as False in Strict mode). HostNameInCertificate has been added to help some Strict mode scenarios. In the future, the plan is also to add a ServerCertificate setting, which points to a certificate file that will be directly compared to the server certificate for validation. This will allow secure use of any certificate (self-signed, for example) on a connection without opening up the connection to MITM attacks by blanket trusting of all certificates (as TrustServerCertificate=true would do).
The currently proposed code also retains API backwards compatibility in SqlConnectionStringBuilder.Encrypt. Encrypt has changed from a bool to a SqlConnectionEncryptOption object, but implicit conversion operators have been added so that it can still be treated like a bool. Comparing SqlConnectionEncryptOption to a bool will return true for SqlConnectionEncryptOption.Mandatory or SqlConnectionEncryptOption.Strict. Converting a bool to SqlConnectionEncryptOption will produce SqlConnectionEncryptOption.Mandatory for true and SqlConnectionEncryptOption.Optional for false.