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

Access Token Support for SqlConnection in Manual Tests #2654

Closed

Conversation

dauinsight
Copy link
Contributor

@dauinsight dauinsight commented Jul 8, 2024

In our manual tests we are changing the way we authenticate to Azure SQL DB by using managed identity.

In order to support this, when we make a connection from a macOS container, we must assign an access token to the connection. This authorizes the instance with access to the managed identity and hence the DB.

A few tests have been skipped (when running macOS) as we're unable to assign the access token to the connection from the test.

@dauinsight dauinsight added the Area\Tests Issues that are targeted to tests or test projects label Jul 8, 2024
@David-Engel
Copy link
Contributor

This seems like it would make it difficult to change test connection strings due to validation on the AccessToken property. What problem is this PR solving?

@dauinsight
Copy link
Contributor Author

This seems like it would make it difficult to change test connection strings due to validation on the AccessToken property. What problem is this PR solving?

Since assigning a UAMI directly to an agent pool is not recommended + our Mac agent pool, we are using a service connection in ADO and have federated credentials to it on a UAMI which has access to the database. As a result, we have an Azure Cli step in ADO to retrieve the token and assign it to the connection to authenticate.

Copy link
Contributor

@arellegue arellegue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Comment on lines 27 to 33
using (SqlConnection con = (SqlConnection) provider.CreateConnection())
{
// Testing in macOS Azure SQL container requires access token authentication
if (!DataTestUtility.AuthenticatingWithoutAccessToken)
{
con.AccessToken = DataTestUtility.AADAccessToken;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you introduce an extension method your change will be simpler without repeating yourself.

Suggested change
using (SqlConnection con = (SqlConnection) provider.CreateConnection())
{
// Testing in macOS Azure SQL container requires access token authentication
if (!DataTestUtility.AuthenticatingWithoutAccessToken)
{
con.AccessToken = DataTestUtility.AADAccessToken;
}
using (DbConnection con = provider.CreateConnectionEx())
static DbConnection CreateConnectionEx(this DbProviderFactory provider)
{
    DbConnection conn = provider.CreateConnection();

    // Testing in macOS Azure SQL container requires access token authentication
    if (!DataTestUtility.AuthenticatingWithoutAccessToken && conn is SqlConnection sqlConn)
    {
        sqlConn.AccessToken = DataTestUtility.AADAccessToken;
        return sqlConn;
    }
    return conn;
}

Comment on lines 122 to 126
public static bool AuthenticatingWithoutAccessToken
{
get
{
return !RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
Copy link
Contributor

@DavoudEshtehari DavoudEshtehari Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes more sense with positive naming. Feel free to modify the naming as you like.

Suggested change
public static bool AuthenticatingWithoutAccessToken
{
get
{
return !RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool ForceAuthenticationWithAccessToken
{
get
{
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);

@DavoudEshtehari DavoudEshtehari self-requested a review July 17, 2024 23:22
@@ -186,7 +186,8 @@ public void ExecuteReaderWithCommandBehaviorTest()
}

// Synapse: Parse error at line: 1, column: 27: Incorrect syntax near 'for'.
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
// Skip: Access token authentication does not work with RemoteExecutor (#31362)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing is applied for the similar comments.

Suggested change
// Skip: Access token authentication does not work with RemoteExecutor (#31362)
// Skip: Access token authentication does not work with RemoteExecutor (active issue #31362)

{
Console.WriteLine($"Generating new access token...");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Console.WriteLine($"Generating new access token...");

Comment on lines 83 to 84
Console.WriteLine($"TCPConnectionString = {DataTestUtility.TCPConnectionString}");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Console.WriteLine($"TCPConnectionString = {DataTestUtility.TCPConnectionString}");

{
string currentDb = connection.Database;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the point of this test is building and reading the connection string properties from a SqlConnection.ConnectionString. So, instead of skipping these kind of tests you can force the using of the DataTestUtility.TCPConnectionString through the helper method by adding a new argument and bypassing the internal logic.

Suggested change
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString);
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);

{
// Testing in macOS Azure SQL container requires access token authentication
if (!DataTestUtility.AuthenticatingWithoutAccessToken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add another property in DataTestUtility and keep it positive, instead of multiplying a negative property by adding a negative sign, which is a bit tricky to follow.

Comment on lines 14 to 15
// Skip: We cannot assign an access token to the connection in this test (#31362)
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.AuthenticatingWithoutAccessToken))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make sense to me that authentication could affect this test. Could you double-check it?

@@ -53,7 +53,7 @@ public static void LocalDBMarsTest()
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void InvalidLocalDBTest()
{
using var connection = new SqlConnection(s_badConnectionString);
using var connection = DataTestUtility.GetSqlConnection(s_badConnectionString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocalDb doesn't require Access Token for authentication.

Suggested change
using var connection = DataTestUtility.GetSqlConnection(s_badConnectionString);
using var connection = new SqlConnection(s_badConnectionString);

@@ -189,7 +189,7 @@ private static void ConnectionTest(string connectionString)

private static void OpenConnection(string connString)
{
using SqlConnection connection = new(connString);
using SqlConnection connection = DataTestUtility.GetSqlConnection(connString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using SqlConnection connection = DataTestUtility.GetSqlConnection(connString);
using SqlConnection connection = new(connString);

@@ -16,7 +16,7 @@ public class KerberosTests
public void IsKerBerosSetupTestAsync(string connectionStr)
{
KerberosTicketManagemnt.Init(DataTestUtility.KerberosDomainUser, DataTestUtility.KerberosDomainPassword);
using SqlConnection conn = new(connectionStr);
using SqlConnection conn = DataTestUtility.GetSqlConnection(connectionStr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change shouldn't be required.

Suggested change
using SqlConnection conn = DataTestUtility.GetSqlConnection(connectionStr);
using SqlConnection conn = new(connectionStr);

@@ -58,7 +58,7 @@ public static void IntegratedAuthenticationTest_ServerSPN()

private static void TryOpenConnectionWithIntegratedAuthentication(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlConnection connection = DataTestUtility.GetSqlConnection(connectionString))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Integrated auth doesn't required access token.

Suggested change
using (SqlConnection connection = DataTestUtility.GetSqlConnection(connectionString))
using (SqlConnection connection = new SqlConnection(connectionString))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class skips Azure and could be reverted.

@@ -43,7 +43,8 @@ public class TvpTest
private readonly string _connStr;

// Synapse: The statement failed. Column 'blob' has a data type that cannot participate in a columnstore index.
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
// TODO: Investigate access token authentication to Azure SQL DB (#31362)
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.AuthenticatingWithoutAccessToken))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you revert all the TVPTest related changes under the ParameterTest folder since they've already skipped on Mac?

@David-Engel
Copy link
Contributor

This seems like it would make it difficult to change test connection strings due to validation on the AccessToken property. What problem is this PR solving?

Since assigning a UAMI directly to an agent pool is not recommended + our Mac agent pool, we are using a service connection in ADO and have federated credentials to it on a UAMI which has access to the database. As a result, we have an Azure Cli step in ADO to retrieve the token and assign it to the connection to authenticate.

@dauinsight

Can we simply use Authentication=ActiveDirectoryDefault for the connection string on the mac jobs and take advantage of the fact that AzureCliCredential is part of the credential chain?

I feel like requiring all tests to wrap their connection strings in DataTestUtility.GetSqlConnection is adding unnecessary complexity to test configuration.

If AD Default doesn't work, another alternative that would be much simpler/cleaner is to create a custom authentication provider that overrode the ActiveDirectoryMSI option to return the access token configured on the job.

Copy link

codecov bot commented Jul 22, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 71.63%. Comparing base (f7ab115) to head (db38ffb).
Report is 29 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2654      +/-   ##
==========================================
- Coverage   72.81%   71.63%   -1.19%     
==========================================
  Files         311      308       -3     
  Lines       61694    61532     -162     
==========================================
- Hits        44922    44077     -845     
- Misses      16772    17455     +683     
Flag Coverage Δ
addons 92.88% <ø> (ø)
netcore 75.94% <ø> (-1.25%) ⬇️
netfx 69.62% <ø> (-0.82%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@dauinsight
Copy link
Contributor Author

This seems like it would make it difficult to change test connection strings due to validation on the AccessToken property. What problem is this PR solving?

Since assigning a UAMI directly to an agent pool is not recommended + our Mac agent pool, we are using a service connection in ADO and have federated credentials to it on a UAMI which has access to the database. As a result, we have an Azure Cli step in ADO to retrieve the token and assign it to the connection to authenticate.

@dauinsight

Can we simply use Authentication=ActiveDirectoryDefault for the connection string on the mac jobs and take advantage of the fact that AzureCliCredential is part of the credential chain?

I feel like requiring all tests to wrap their connection strings in DataTestUtility.GetSqlConnection is adding unnecessary complexity to test configuration.

If AD Default doesn't work, another alternative that would be much simpler/cleaner is to create a custom authentication provider that overrode the ActiveDirectoryMSI option to return the access token configured on the job.

@David-Engel I agree that this adds a little bit of complexity, but i haven't found another way to use our service connection in ADO to grant MI to the agent using an access token.

ActiveDirectoryDefault works locally if we login to our machine via az login but not in the pipeline.

For more context into the implementation of this PR: https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Engineering/675144/SFI-MI?anchor=mi-for-e2es-in-dev-environment-and-azure-devops(ado)

@David-Engel
Copy link
Contributor

@dauinsight

For more context into the implementation of this PR: https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Engineering/675144/SFI-MI?anchor=mi-for-e2es-in-dev-environment-and-azure-devops(ado)

After reading that, in addition to the custom provider option I mentioned, prioritizing #2557 could also provide a better solution that could be used. As it is, I would NOT merge this PR. Dynamically modifying the connection string like that across all tests is just asking for maintenance headaches and added cost.

Additionally, I noticed the AZURE_DB_SP_TCP_CONN_STRING was removed from the test config variable group. That could have been used for macOS, too. We have an exception for that for continuing to test AD SP auth.

CC: @saurabh500 @cheenamalhotra

@saurabh500
Copy link
Contributor

I strongly agree with @David-Engel
Unintentional modification of the connection string puts too many hinderances in test enhancements.

@dauinsight
Copy link
Contributor Author

Okay that makes sense, for now i'll look to have the service principal set up again.

@dauinsight dauinsight closed this Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area\Tests Issues that are targeted to tests or test projects
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants