Skip to content
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 | Split Azure dependent functionality in a separate NuGet Package #1108

Open
SimonCropp opened this issue Jun 9, 2021 · 111 comments
Open
Assignees
Labels
💡 Enhancement Issues that are feature requests for the drivers we maintain. 🔨 Breaking Change Issues/PRs that are related with breaking API changes in the driver. 🆕 Public API Issues/PRs that introduce new APIs to the driver.
Milestone

Comments

@SimonCropp
Copy link
Contributor

why does Microsoft.Data.SqlClient v3 reference Azure.Identity?

@SimonCropp SimonCropp changed the title Why is Azure.Identity referenced Why is Azure.Identity referenced? Jun 9, 2021
@SimonCropp
Copy link
Contributor Author

found my answer #1010. so will close this. it still seems very weird to me that a sql client should have a dependency on a cloud infrastructure library. For hypothetical comparison, would it be ok to also take dependencies on google and aws libraries.

@David-Engel
Copy link
Contributor

@SimonCropp Microsoft.Data.SqlClient also provides connectivity to Azure cloud services (Azure SQL DB, Azure Synapse Analytics, etc) that are very similar to SQL Server on-premise. The Azure.Identity library provides authentication functionality for those Azure services.

@virzak
Copy link

virzak commented Jun 15, 2021

I was also wondering if there is a way to separate functionality into multiple DLLs, i.e.

  • Microsoft.Data.SqlClient
  • Microsoft.Data.SqlClient.Azure

With .Net 5, a project which uses this library and doesn't use Azure gets at least 3 new unused DLLs in the bin directory:
Azure.Core.dll
Azure.Identity.dll
Microsoft.Bcl.AsyncInterfaces.dll

@SimonCropp
Copy link
Contributor Author

i had the same expectation as @virzak

@virzak
Copy link

virzak commented Jun 17, 2021

@SimonCropp reopen then? :)

@SimonCropp
Copy link
Contributor Author

@virzak i think i have raised my concerns. if the sql client team think they should be address, then they can re-open the issue

@cheenamalhotra
Copy link
Member

We are considering this internally, timeline not certain yet. But we can reopen for sure!

@cheenamalhotra cheenamalhotra changed the title Why is Azure.Identity referenced? Feature | Split Azure dependent functionality in a separate NuGet Package Jul 7, 2021
@mtaylorfsmb
Copy link

Agree with others. This library is supposed to be replacement for the standard library already in the framework. Azure support is fine but shouldn't be auto-included in a "core" package. This would be equivalent to having the core framework depend upon Azure as well. If my app is using AWS or GCP then having an Azure dependency looks wrong.

@aersam
Copy link

aersam commented Sep 17, 2021

Agree, too. Mostly because of the dependencies Azure.Identity brings in. There are really many packages needed for Microsoft.Data.SqlClient

@MarkPflug
Copy link

FWIW, I also just wasted a bunch of time tracking down why these Azure assemblies were being included in my build output. I'm not using anything Azure related and so it made me worried some malicious package had snuck into my dependency graph. Took me a while to figure out that MDS was the culprit. It was annoying enough to me that I decided to switch back to SDS, which is probably not something y'all want to encourage.

@wastaz
Copy link

wastaz commented Jan 10, 2022

Agree on this as well. Azure support should be opt-in and a separate thing. Wasted a decent amount of time today again in trying to figure out why my solution that has nothing to do with azure was referencing azure packages.

@guillaume86
Copy link

Just noticed this because of some versions conflicts on packages that seemed completely unrelated to SqlClient (OpenIdConnect).
Azure dependencies should definitely be moved out in another package.

@DavoudEshtehari DavoudEshtehari added this to the Future milestone Jan 14, 2022
@m-gallesio
Copy link

Agreeing on this.

Comparing the dependencies of Microsoft.Data.SqlClient with those of the classic System.Data.SqlClient, the new library adds some more other than Azure which should be not needed in at least some scenarios:

  • Microsoft.Identity.Client
  • Microsoft.IdentityModel.JsonWebTokens
  • Microsoft.IdentityModel.Protocols.OpenIdConnect

The following also seem to be meaningful only for Windows:

  • Microsoft.Win32.Registry
  • System.Security.Principal.Windows

I do not know how heavy those libraries are and whether their presence might stem from simple refactorings, but I guess their presence in a core package should be evaluated.

@m-gallesio
Copy link

As someone who does not know the internals of this project at all, I tried downloading the code and simply looking for those package names via CTRL+F in *.cs files. Here are some results:

  • Azure.Identity, Azure.Core:
    • add-ons/AzureKeyValueProvider (seems normal)
    • ActiveDirectoryAuthenticationProvider
    • test code
  • Microsoft.Identity.Client
    • SqlInternalConnectionTds
    • ref directories (I do not know what these are for)
    • ActiveDirectoryAuthenticationProvider
    • test code
  • Microsoft.IdentityModel.JsonWebTokens, Microsoft.IdentityModel.Protocols.OpenIdConnect
    • AzureAttestationEnclaveProvider
  • Microsoft.Win32.Registry
    • no apparent usage in non-test code
    • tests/AlwaysEncrypted
  • System.Security.Principal.Windows
    • no apparent usage at all

Overall the "offending" packages seem quite well-encapsulated.

@AraHaan
Copy link
Member

AraHaan commented May 2, 2022

I think I might see if I can make a local clone of sqlclient and see what I can do to open an pr to move all azure dependencies into their own package where one must append .Azure to the end of the SqlClient package name to get them (where the Azure version references the normal sqlclient package so you can use the normal one as well as a convenience.

This is because I also face this issue where I do not use Azure stuff at all (however I do use efcore).

As for win32.Registry that package is supposed to come from the ref pack that is included by default for the default runtime that ships with the .NET SDK. Same for System.Security.Principal.Windows. I have an pull request that removes the packages for .NET Core/5 and 6+ applications however it is blocked for now until they can upgrade the CI to install the .NET 6 SDK everywhere so the build of that pull request will all pass.

If I do split them it would help eliminate even more dependencies that cause me pain which would always be good for all of us.

Also note: when I do the split I will also upgrade it to the latest version of the Azure SDK because they recommend always keeping it up with the latest stable.

As for .NET Framework, I am not sure if I should make any changes to it since I do not really care for .NET Framework at this point.

@AraHaan
Copy link
Member

AraHaan commented May 3, 2022

Bad news, the Azure Enclave Provider is used in EnclaveDelegate.Crypto.cs which I do not know for sure if that file is only for Azure or not. This has made it a lot harder to separate it.

Luckily it looks like TDS is some sort of remote specific stuff? Perhaps Azure specific?

@AraHaan
Copy link
Member

AraHaan commented May 3, 2022

It looks like Azure cannot be split because it's to far engraved into the dependencies of basically everything in SqlClient needlessly.

At least currently it just does not seem possible at the moment.

@m-gallesio
Copy link

m-gallesio commented May 4, 2022

the Azure Enclave Provider is used in EnclaveDelegate.Crypto.cs

I tried taking a quick look at this and AzureAttestationEnclaveProvider.cs despite its name does not seem to explicitly reference Azure packages.
As I noted before it does require several Microsoft.IdentityModel packages, though.

@AraHaan
Copy link
Member

AraHaan commented May 4, 2022

Yeah, I tried to split that into Microsoft.Data.SqlClient.Azure so the IdentityModel packages would move to that project (and package)

@AraHaan
Copy link
Member

AraHaan commented May 4, 2022

Alternatively I could rig it up to where it could look for enclaves providers registered with DependencyInjection however that would add another dependency and obtain the enclaves providers registered there everywhere they are needed (however I already use Dependency Injection significantly so it does not bother me). However it would bother someone who is not using Dependency Injection but is using SqlClient.

@ian-cowley
Copy link

Would the unused elements of this get stripped at compile time, with trimming options?

@mtaylorfsmb
Copy link

@ErikEJ Correct me if I'm wrong but there will still come a day when you're back to the first scenario I mentioned. Code that relies on MDS.Azure (metapackage) which relies on MDSv1 will updated to MDS.Azure.vNext (shipping the binaries directly). But projects that rely on MDSv1 transitively (for whatever reason) will suddenly have a conflict when they update MDS.Azure.vNext because 2 different packages now have the same binaries. It is just kicking the problem down the road, or maybe I don't understand how this is going to work ultimately.

@edwardneal
Copy link
Contributor

I'd recommend trying to think about exposing an authentication plugin API, which external packages (such as M.D.S.Azure) would be able to hook into in order to do their work. In the ideal design, this extensibility would be exposed on SqlDataSourceBuilder - and M.D.S.Azure would provide a simple extension method over SqlDataSourceBuilder to do the appropriate configuration - but some global/static way to register the plugin would also be necessary for anyone not using SqlDataSourceBuilder.

@roji a lot of this work has technically already been done. There's a public SqlAuthenticationProvider interface, backed by SqlAuthenticationProviderManager. There might be a need to refactor the AD authentication provider, but that's not a change to public API surface. An additional piece of API surface would be needed for the AlwaysEncrypted enclave providers to modify GetEnclaveProvider, but this could use a similar public-facing design.

I thought a little about what that'd require, whether M.D.S should treat M.D.S.Azure uniquely because it was embedded inside the core library, etc. It's a bit of a moot point though - I didn't think it'd be ready in time for an early enough preview of M.D.S v6, which is where the idea around a metapackage and possibly an empty piece of public API surface comes from.

This does all depend on whether we can make transitive dependencies work as they need to though. From checking NuGet's dependency resolution rules @mtaylorfsmb, I think there'd only be one problem to consider?

We can safely expect that M.D.S.Azure wouldn't always be a metapackage, that during its time as a metapackage its version would be kept in lockstep in M.D.S, and it would explicitly pin its reference to exactly the same version of M.D.S. This would mean that we're not going to run into transitive security vulnerabilities from the metapackage - it'd be published from the same repository at the same time as M.D.S.

The scenarios I have in mind for the dependencies are:

  1. Project references M.D.S.Azure v6.1 and Package B. Package B references M.D.S >= v6.0. M.D.S v6.1 will be used because it's the lowest version which satisfies both dependencies.
  2. Project references M.D.S.Azure >= v6.1 and Package B. Package B references M.D.S v6.0. This'd be a new kind of package conflict, since M.D.S would no longer be a direct dependency of the project (and would thus no longer override Package B's dependencies.)
  3. Project references Package A and Package B. Package A references M.D.S.Azure v6.1 and Package B references M.D.S v6.0. This isn't a new package conflict - it'd need to be resolved in the normal way.

I'm not pleased with scenario 2. Even in that scenario though, if a developer migrates to M.D.S.Azure, sees this problem and adds another extra reference to M.D.S, they've still put themselves into a non-breaking position for v7.0. We'd need to document that, so they don't immediately revert back to referencing M.D.S exclusively. I could also see it being frustrating for downstream libraries referencing M.D.S.

The really "interesting" point comes when we switch from a metapackage to something with real content. In scenario 2, a plugin route's actually got a pretty simple exit ramp: we'd bump M.D.S and M.D.S.Azure to v7, loosen M.D.S.Azure's dependencies to reference M.D.S >= v7, and the scenario becomes irrelevant. If there's a second copy of the M.D.S binaries in the M.D.S.Azure package, I'm not sure which binaries would be used, or what might happen in more complicated package dependency trees with multiple versions of M.D.S and M.D.S.Azure. If that made it through the compiler, I wouldn't be too surprised if we found that the application code was using M.D.S.Azure, but that the libraries referencing M.D.S were using M.D.S.

While not a strong preference, I'd personally prefer the plugin approach in the long run because I think it'd make stepping M.D.S.Azure from a metapackage to a real package simpler.

@thompson-tomo
Copy link

I also think a plugin approach is the correct approach in the long term but understand the timing pressurws.

Is there a way that we could perhaps implement the plugin method but only sets a variable. Then when we pass in an azure connection string a warning is logged if that variable is not set?

@roji
Copy link
Member

roji commented Aug 28, 2024

@ErikEJ @edwardneal to make sure we're talking about the same things, my comments below are about the proposal of (eventually) having a copy-paste, i.e. both M.D.S and M.D.S.Azure containing e.g. a SqlConnection type, and without M.D.S.Azure referencing M.D.S.

In that scenario, what would e.g. the EF SQL Server provider reference? Either:

  1. EF references M.D.S, in which case EF users can't connect to Azure - obvious non-starter.
  2. EF references M.D.S.Azure, in which case all EF users get the Azure dependencies again, regardless of whether they use Azure or not. That's exactly what this change is supposed to fix, so again, not good.
  3. EF now has to publish two providers, mirroring the SqlClient split: Microsoft.EntityFrameworkCore.SqlServer and M.EF.SqlServer.Azure. That's really something we should try to avoid; plus, this same problem gets pushed up once again for any library/plugin/extensions out there that wants to work with the EF SQL Server provider.

This is related to the difficulties @mtaylorfsmb pointed out above... People would very easily find themselves with both SqlClient packages somewhere in the dependency graph, and at that point you have two versions of SqlConnection and things simply become unmanageable.

In contrast, if M.D.S.Azure is simply a plugin which only contains Azure-related auth stuff, and references M.D.S, then EF (and any other library) would simply reference M.D.S, and the user would decide whether to also reference M.D.S.Azure.

@roji I understand your thoughts, but I think the switch to .Azure should only require a package change. New external dependencies are implemented as plug ins today, like the KeyVault provider.

I understand and appreciate the desire to make this transition as painless as possible... But note that this whole issue (splitting Azure.Identity out of the base M.D.S) has been held back up to now because of (legitimate) backwards compats concerns. Now that we have a general willingness to explore a breaking change here, I really hope we don't produce a problematic, sub-standard solution again for the same reasons... If we're already breaking (via the package reference), IMHO might as well do things really right even if it means requiring users to paste another line of code somewhere...

@ErikEJ
Copy link
Contributor

ErikEJ commented Aug 29, 2024

@roji Got it! And then provide an actionable error message with a link to good docs when attempting to use Azure auth with the "base" package...

@edwardneal
Copy link
Contributor

That sounds sensible to me too. I've seen #2823 and it looks good to me, thanks for opening that. If the SqlClient team are happy with the design, it'd be good to publish it as part of the next preview of v6.0 so we can start the split

@roji
Copy link
Member

roji commented Aug 29, 2024

And then provide an actionable error message with a link to good docs when attempting to use Azure auth with the "base" package...

Yes, exactly! As long as you provide crystal clear fail-fast behavior with an informative exception pointing to a good page explaining what to do to (switch to M.D.S.Azure, add this in your code), I really think it's a very reasonable situation... When this is possible, it's the "best kind" of breaking change (compared to breaking changes where the behavior silently changes or whatever). Users will still complain (some will complain no matter what), but the majority will simply do the change quickly and move on.

@roji
Copy link
Member

roji commented Aug 29, 2024

BTW if you do go with the plugin approach, I'm not sure there's value in actually publishing an M.D.S.Azure package (as in #2823) before it's fully implemented; that wouldn't be very useful, given that users will have to add the code gesture anyway when the work is fully done. So rather than telling users to switch to M.D.S.Azure today, and then add the code gesture tomorrow, you might as well just publish M.D.S.Azure tomorrow and tell them to do both at the same time?

@saurabh500
Copy link
Contributor

saurabh500 commented Aug 29, 2024

@roji The plugin approach makes most sense to me.

Also, I would like to stay away from the re-branding of any SqlClient to SqlClient for Azure.
There are features which were offered in Azure SQL MI/DB which have made their way into Sql On prem, e.g. Entra ID auth in Sql 2022.

This means that there is only one client with extensions/plugins to help light up the added scenarios in Az SQL DB/On prem, but apps / ORMs like EF only care about one package i.e. MDS, which remains the gateway into SQL Server.

Of course, all this while solving the core problem at hand, i.e. Azure related dependencies should be optional for those who don't need them.

This effort should look at

  1. Extracting Entra ID auth out of MDS and creating a new Nuget Package.
  2. Due to Enclaves, if there are dependencies, those should be extracted out.
  3. Anything else that I may have missed.

I propose that MDS vNext, should remove the auth providers for atleast Entra ID auth (AAD auth), out of the code.

I had a chat with @David-Engel and he mentioned that there are dependencies on MSALException for retry of token fetching, and there are few other capabilities for which the driver may have taken a dependency on the internals of the AUth providers.
These will need to be encapsulated in the base class. SqlAuthenticationProvider and if needed the SqlAuthenticationProviderManager as well.

Hence we will have MDS and M.D.S.Azure.Authentication.

MDS.Az.Auth depends on MDS (for the provider base classes) and Azure libraries.

vNext Experience:

  1. Customers take MDS as a dependency.
  2. Those who need Entra ID auth, get a runtime exception with actionable error message which has been hinted in the discussion.
  3. The app authors will then add a dep to MDS.Az.Auth.

For those who dont care about azure related capabilities: Just upgrade to MDS vNext

At this point, we still need to make MDS aware about the MDS.Az.Auth providers

  1. Expose a static API to register the Az Auth provider. (I am not sure if it exists already, likely does)
  2. There will be friction to make the code change in app owners. In that case MDS could look at "MDS.Az.Auth" assembly by name, and load the providers if the assembly is found. This would make the experience nicer, where the app owners only add a dependency to MDS.Az.Auth, and let it bring in the Az dependencies. This basically means that MDS gives first preferential treatment to MDS.Az.Auth by looking it up, in the absence of any providers.

And yes, we need to look into the behavior with SqlDataSource as well. This is a classic case of leveraging the data source builder, but I first want to focus on the declarative way of making this work as well a NetFx users.

@saurabh500
Copy link
Contributor

Though I have tagged @roji, I would like to invite comments from @edwardneal and @ErikEJ who have gone into the depths of this implementation, about the feasibility of the proposal.

@edwardneal
Copy link
Contributor

Thanks for this @saurabh500. I hadn't actually spotted the extra dependency on MSALException, but that's correct. For everyone's reference, this is largely in SqlInternalConnectionTds.GetFedAuthToken, which hasn't yet been merged between .NET Core and .NET Framework but is almost identical.

In terms of the vNext (and the v6) experience: looking solely at logistics, I'm a little concerned that we could end up telling developers to migrate from SDS to MDS, then to add another reference to MDS.Az.Auth six months later. My main reason for asking for the front-loading of the extra package was to be able to simplify that and immediately tell migrators that they should use MDS if they use on-premise servers and MDS.Az if they use Azure SQL (or other Azure components.)

To lift that same motive to a plugin model: would there be enough time to provide a v6 MDS.Az.Auth package consisting of nothing but an empty API surface? This'd provide migrators from SDS with a single choice to make, and would give existing users of MDS a full major version to bring their dependencies into line.

Splitting into two packages in time for vNext definitely sounds good to me. I think there are a few pieces of work I can help with, pending any comments.

@ErikEJ
Copy link
Contributor

ErikEJ commented Aug 29, 2024

telling developers to migrate from SDS to MDS, then to add another reference to MDS.Az.Auth six months later

I think most developers at this point already use MDS, actually

@ErikEJ
Copy link
Contributor

ErikEJ commented Aug 29, 2024

Splitting into two packages in time for vNext definitely sounds good to me. I think there are a few pieces of work I can help with, pending any comments.

I don't see why all of this could not be done for vNext given there is resources to implement it.

@saurabh500
Copy link
Contributor

It seems feasible to me.
I need to chat with @David-Engel about some of the internal logistics around releasing new packages. I don't understand that part too well.

Also I would like to eventually convert this issue to a discussion and open a new issue with the exact proposal in the description and link that to PR(s)

We can divide and conquer this. I will reach out for help with some of the items.

@MichaelKetting
Copy link

MichaelKetting commented Aug 30, 2024

telling developers to migrate from SDS to MDS, then to add another reference to MDS.Az.Auth six months later

I think most developers at this point already use MDS, actually

@ErikEJ I can't object to that statement but would like to qualify that this likely means developers who do in-house develpment instead of ISVs. For us, MDS is currently a no-go due to the dependencies and we cannot migrate our products from SDS to MDS until the dependency problem has been solved.

@ernstscheithauer
Copy link

@MichaelKetting +1 from my side. This dependency in combination with the necessary security updates is a pain, preventing us from migrating to MDS as well. Having software installed on-prem (not having a single SAAS instance) requires stability that is lacking there.

@cheenamalhotra cheenamalhotra added 💡 Enhancement Issues that are feature requests for the drivers we maintain. 🆕 Public API Issues/PRs that introduce new APIs to the driver. 🔨 Breaking Change Issues/PRs that are related with breaking API changes in the driver. and removed 🙌 Up-for-Grabs Issues that are ready to be picked up for anyone interested. Please self-assign and remove the label labels Oct 1, 2024
@cheenamalhotra cheenamalhotra modified the milestones: Future, 6.0.0 Oct 5, 2024
@cheenamalhotra
Copy link
Member

Feature branch for capturing progress: feat/azure-split

@ErikEJ
Copy link
Contributor

ErikEJ commented Oct 5, 2024

Awesome!! Great to see you back on the team, @cheenamalhotra

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💡 Enhancement Issues that are feature requests for the drivers we maintain. 🔨 Breaking Change Issues/PRs that are related with breaking API changes in the driver. 🆕 Public API Issues/PRs that introduce new APIs to the driver.
Projects
None yet
Development

No branches or pull requests