question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DB queries hang when adding password fallback mechanism to primary AAD connection

See original GitHub issue

We are currently facing issues when trying to implement fallback logic in the way we connect to our Azure SQL database from our Azure cloud services.

We currently connect to our Azure SQL DB using AAD authentication. What we are trying to do is implement fallback logic for this connection, where if there is a failure in the initial connection (ex. when acquiring the AAD access token), we fallback to a secondary connection. The secondary connection uses username + password authentication.

The issue we are facing is that on an Azure cloud service using EF6, the fallback path doesn’t work. What we get is that the connection itself is initialized successfully, but any DB call via EF hangs forever with no response. During the investigation, we have done the following:

  1. Trying the same on an Azure function app, it works. So it seems to only fail on Azure cloud services.
  2. Issue is not reproducible locally, only on a remote Azure Cloud service.
  3. When using username + password as primary authentication (no AAD connection anymore), things work, so it seems that it is not an issue with password authentication on Azure cloud services in general.
  4. Examining long running queries on our Azure SQL instance, we don’t find the queries that the cloud service indicates that are hanging. So the queries seem to never reach the DB.

Steps to reproduce

Code snippet for initial AAD logic we had:

public static SqlConnection InitializeSqlConnectionWithAad(
            string dbConnectionString,
            string dbClientIdThumbprintPair)
        {
            var accessToken = AsyncHelpers.RunSync(() => GetAccessToken(dbClientIdThumbprintPair));

            var connection = new SqlConnection(dbConnectionString)
            {
                AccessToken = accessToken
            };
            if (connection.AccessToken != null)
            {
                connection.Open();
            }

            return connection;
        }

private static async Task<string> GetAccessToken(
            string dbClientIdThumbprintPair)
        {
            if (string.IsNullOrWhiteSpace(dbClientIdThumbprintPair))
            {
                return null;
            }

            var credentialsPair = dbClientIdThumbprintPair.Split(';');
            if (credentialsPair.Length != 2)
            {
                Logger.LogError("Invalid Client ID Thumbprint pair provided for database connection");
                throw new Exception("Invalid Client ID Thumbprint pair provided for database connection");
            }

            var clientId = credentialsPair[0];
            var clientThumbprint = credentialsPair[1];
            var cert = CertificateHelper.FindCertificateByThumbprint(clientThumbprint);

            if (cert == null)
            {
                Logger.LogError("Certificate not found for database connection");
                throw new Exception("Certificate not found for database connection");
            }

            var clientCertificate = new ClientAssertionCertificate(clientId, cert);
            string aadInstance = ConfigAccessor.GetSetting("OAuthAADInstance");
            string tenant = ConfigAccessor.GetSetting("OAuthAADTenant");
            string authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, aadInstance, tenant);
            string dbAppURI = ConfigAccessor.GetSetting("DBResourceURI");

            var authContext = new AuthenticationContext(authority);
            var authenticationResult = await authContext.AcquireTokenAsync(
                dbAppURI,
                clientCertificate,
                sendX5c: true).ConfigureAwait(false);

            return authenticationResult.AccessToken;
        }

Code snippet for fallback logic (When an exception is thrown from AAD path, the fallback part with password is the path having issues):

public static SqlConnection InitializeSqlConnection(
            string dbConnectionString,
            string dbClientIdThumbprintPair,
            string dbConnStrWithPassword)
        {
            SqlConnection connection = null;
            bool aadConnectionSucceeded;

            try
            {
                connection = InitializeSqlConnectionWithAad(dbConnectionString, dbClientIdThumbprintPair);
                aadConnectionSucceeded = true;
            }
            catch (Exception ex)
            {
                Logger.LogError("Sql AAD connection failed. Falling back to secondary connection with password.", ex.ToString());
                aadConnectionSucceeded = false;
            }

            if (!aadConnectionSucceeded)
            {
                try
                {
                    connection = new SqlConnection(dbConnStrWithPassword);
                }
                catch (Exception ex)
                {
                    Logger.LogError("Sql password fallback connection failed.", ex.ToString());
                    throw;
                }
            }

            return connection;
        }

Further technical details

EF version: 6.3 Target framework: Net Framework 4.6.2 Operating system: Windows Server 2016 IDE: Not reproducible locally

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
AmirGeorgecommented, Jul 8, 2020

@ajcvickers turned out to be an issue in our client code that we fixed, so we can close this issue.

For future reference in case anyone faces a similar issue, the problem was in the implementation of AsyncHelpers.RunSync which is provided in many online resources. The original implementation is as follows:

public static T RunSync<T>(Func<Task<T>> task)
        {
            var oldContext = SynchronizationContext.Current;
            var synch = new ExclusiveSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(synch);
            T ret = default(T);
            synch.Post(
                async _ =>
                {
                    try
                    {
                        ret = await task();
                    }
                    catch (Exception e)
                    {
                        synch.InnerException = e;
                        throw;
                    }
                    finally
                    {
                        synch.EndMessageLoop();
                    }
                }, null);
            synch.BeginMessageLoop();
            SynchronizationContext.SetSynchronizationContext(oldContext);
            return ret;
        }

Now the issue with the above is that the synchronization context was only set back to the old context only if no exception was thrown. If an exception was thrown, this doesn’t happen which risks deadlocks, which happened in our case. Now to solve the issue, we simply moved the restoring of the synchronization context to be inside a finally statement:

public static T RunSync<T>(Func<Task<T>> task)
        {
            var oldContext = SynchronizationContext.Current;
            var synch = new ExclusiveSynchronizationContext();

            try
            {
                SynchronizationContext.SetSynchronizationContext(synch);
                T ret = default(T);
                synch.Post(
                    async _ =>
                    {
                        try
                        {
                            ret = await task();
                        }
                        catch (Exception e)
                        {
                            synch.InnerException = e;
                            throw;
                        }
                        finally
                        {
                            synch.EndMessageLoop();
                        }
                    }, null);
                synch.BeginMessageLoop();
                return ret;
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(oldContext);
            }
        }
1reaction
ajcvickerscommented, Jun 10, 2020

@AmirGeorge There doesn’t seem anything directly related to EF here–it’s all code that uses SqlClient only. Is there a specific reason that you filed this as an EF issue?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Implement password hash synchronization with Azure AD ...
To synchronize your password, Azure AD Connect sync extracts your password hash from the on-premises Active Directory instance.
Read more >
Database administration — MIT Kerberos Documentation
A Kerberos database contains all of a realm's Kerberos principals, their passwords, and other administrative information about each principal.
Read more >
Ivanti Connect Secure Administration Guide
Specify the password for the LDAP server. Backup Admin DN. Specify the backup administrator DN for queries to the LDAP directory, as a...
Read more >
Oracle Database Release 19c New Features
The updated Create Application Wizard features a new low-code approach to creating applications and simpler, modernized wizards for creating applications.
Read more >
SAP HANA System Replication
Monitoring SAP HANA System Replication with SQL query. ... It is used to close hanging connections on the primary system that are not ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found