dotnet / SqlClient

Microsoft.Data.SqlClient provides database connectivity to SQL Server for .NET applications.
MIT License
842 stars 281 forks source link

Federated Identity Credentials + Managed Identity support #2556

Open yiwwan opened 3 months ago

yiwwan commented 3 months ago

Is your feature request related to a problem? Please describe.

Currently FIC is supported by using workload identity, however it's not compatible with (or is there any existing solutions?) using manage identity directly (FIC + MSI), we need to write our own SqlAuthenticationProvider everywhere and overrides existing authentication method.

Describe the solution you'd like

Add a new authentication method to support FIC + MSI. Basically we'll need a SqlAuthenticationProvider that get authentication results using ClientAssertionCredential and ManagedIdentityCredential:

internal class FICAuthProvider : SqlAuthenticationProvider
{
    private const string DefaultScopeSuffix = "/.default";

    public override async Task<SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters)
    {
        // We can reuse existing environment variables or use new ones
        var msiClientId = Environment.GetEnvironmentVariable("SOME_NEW_ENV_KEY");
        var aadAppClientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
        var resourceTenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");

        var assertion = new ClientAssertionCredential(
            resourceTenantId,
            aadAppClientId,
            async (token) => await GetManagedIdentityToken(msiClientId));

        var scopes = new[] { parameters.Resource.EndsWith(DefaultScopeSuffix) ? parameters.Resource : parameters.Resource + DefaultScopeSuffix };

        var token = await assertion.GetTokenAsync(new TokenRequestContext(scopes));
        return new SqlAuthenticationToken(token.Token, token.ExpiresOn);

        static async Task<string> GetManagedIdentityToken(string msiClientId)
        {
            return (await new ManagedIdentityCredential(msiClientId).GetTokenAsync(new Azure.Core.TokenRequestContext([$"api://AzureADTokenExchange/.default"])).ConfigureAwait(false)).Token;
        }
    }

    public override bool IsSupported(SqlAuthenticationMethod authenticationMethod)
    {
        // Add new member for SqlAuthenticationMethod
        return authenticationMethod == SqlAuthenticationMethod.FederatedIdentityCredentials;
    }
}

Describe alternatives you've considered

N/A

Additional context

N/A

yhvicey commented 3 months ago

Created a PR for this feature, please let me know if there's any concern.

David-Engel commented 3 months ago

Not a concern, per se, but I wonder if Azure.Identity has plans to add ClientAssertionCredential to the DefaultAzureCredential chain. If so, we would only need to bump our dependency version for this to be usable via Authentication=ActiveDirectoryDefault.

yhvicey commented 3 months ago

Raised Azure/azure-sdk-for-net#44463 to see if they have any plan to support this.

christothes commented 2 months ago

could this be implemented using AccessTokenCallback?

scottaddie commented 2 months ago

Not a concern, per se, but I wonder if Azure.Identity has plans to add ClientAssertionCredential to the DefaultAzureCredential chain. If so, we would only need to bump our dependency version for this to be usable via Authentication=ActiveDirectoryDefault.

@David-Engel We have no plans to add ClientAssertionCredential to the DAC chain. See Chris' question above? ☝️

David-Engel commented 2 months ago

could this be implemented using AccessTokenCallback?

Short term, yes, if they control the code. Or they can override any existing Authentication option via a custom authentication provider, if they don't control the code. No one should be blocked. But having it built-in option makes it a lot easier to use.