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 | Azure Active Directory Managed Identity authentication support #730

Merged
merged 27 commits into from Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
300dcc2
Active Directory Managed Identity support
cheenamalhotra Aug 7, 2020
b518319
Merge branch 'master' into azure-managed-identity
cheenamalhotra Sep 2, 2020
f6cc561
Changes - Part 2
cheenamalhotra Sep 3, 2020
3027452
Merge branch 'master' into azure-managed-identity
cheenamalhotra Sep 17, 2020
794ce0d
Merge branch 'master' into azure-managed-identity
cheenamalhotra Sep 18, 2020
3fc79a0
More changes for Managed identity support
cheenamalhotra Sep 18, 2020
b9f9f3e
Fixes
cheenamalhotra Sep 18, 2020
7c89101
Review feedback addressed
cheenamalhotra Sep 18, 2020
b26ebf2
Add Active Directory MSI keyword support
cheenamalhotra Sep 18, 2020
2e815e4
Change to ArgumentException
cheenamalhotra Sep 18, 2020
569470a
Tests for Managed Identity - All pipelines updated
cheenamalhotra Sep 21, 2020
b7d18fb
Fix for .NET Standard + test fix
cheenamalhotra Sep 22, 2020
baa2d21
Minor fix
cheenamalhotra Sep 22, 2020
02728d9
Temporary disable test
cheenamalhotra Sep 22, 2020
6849641
Attempt test fix - works in standalone app
cheenamalhotra Sep 23, 2020
516706d
Disable test on Azure due to concurrency issues
cheenamalhotra Sep 23, 2020
5cbcf5d
Apply suggestions from code review
cheenamalhotra Sep 24, 2020
936b627
Review feedback applied
cheenamalhotra Sep 25, 2020
66d329e
Escape UserId + tests
cheenamalhotra Sep 25, 2020
5924250
Add trace logs
cheenamalhotra Sep 25, 2020
818c906
Fix tests
cheenamalhotra Sep 26, 2020
37c2d5f
Fix test
cheenamalhotra Sep 28, 2020
262d503
Merge branch 'master' into azure-managed-identity
cheenamalhotra Sep 28, 2020
1d01e61
New tests for AppClientId code coverage
cheenamalhotra Sep 28, 2020
6237c9a
No public class.
cheenamalhotra Sep 29, 2020
fc8e2ed
Review feedback applied
cheenamalhotra Oct 6, 2020
543a7cf
Remove unwanted variable
cheenamalhotra Oct 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -88,6 +88,8 @@
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryInteractive%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryServicePrincipal%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryManagedIdentity%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryMSI%2A>|

## Examples
The following example demonstrates providing a custom device flow callback to SqlClient for the Device Code Flow authentication method:
Expand Down
Expand Up @@ -33,5 +33,13 @@
<summary>The authentication method uses Active Directory Device Code Flow. Use Active Directory Device Code Flow to connect to a SQL Database from devices and operating systems that do not provide a Web browser, using another device to perform interactive authentication.</summary>
<value>6</value>
</ActiveDirectoryDeviceCodeFlow>
<ActiveDirectoryManagedIdentity>
<summary>The authentication method uses Active Directory Managed Identity. Use System Assigned or User Assigned Managed Identity to connect to SQL Database from Azure client environments that have enabled support for Managed Identity. For User Assigned Managed Identity, 'User Id' or 'UID' is required to be set to the object ID of the user identity.</summary>
<value>7</value>
</ActiveDirectoryManagedIdentity>
<ActiveDirectoryMSI>
<summary>Alias for "Active Directory Managed Identity" authentication method. Use System Assigned or User Assigned Managed Identity to connect to SQL Database from Azure client environments that have enabled support for Managed Identity. For User Assigned Managed Identity, 'User Id' or 'UID' is required to be set to the object ID of the user identity.</summary>
<value>8</value>
</ActiveDirectoryMSI>
</members>
</docs>
Expand Up @@ -12,8 +12,7 @@
<param name="userId">The user login name/ID.</param>
<param name="password">The user password.</param>
<param name="connectionId">The connection ID.</param>
<summary>Initializes a new instance of the <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationParameters" />
class using the specified authentication method, server name, database name, resource URI, authority URI, user login name/ID, user password and connection ID.</summary>
<summary>Initializes a new instance of the <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationParameters" /> class using the specified authentication method, server name, database name, resource URI, authority URI, user login name/ID, user password and connection ID.</summary>
</ctor>
<AuthenticationMethod>
<summary>Gets the authentication method.</summary>
Expand Down
Expand Up @@ -93,6 +93,10 @@ public enum SqlAuthenticationMethod
ActiveDirectoryServicePrincipal = 5,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/ActiveDirectoryDeviceCodeFlow/*'/>
ActiveDirectoryDeviceCodeFlow = 6,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/ActiveDirectoryManagedIdentity/*'/>
ActiveDirectoryManagedIdentity = 7,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/ActiveDirectoryMSI/*'/>
ActiveDirectoryMSI = 8,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/NotSpecified/*'/>
NotSpecified = 0,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/SqlPassword/*'/>
Expand Down Expand Up @@ -580,12 +584,12 @@ public sealed partial class SqlConnection : System.Data.Common.DbConnection, Sys
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
public System.Guid ClientConnectionId { get { throw null; } }

///
///
/// for internal test only
///
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
internal string SQLDNSCachingSupportedState { get { throw null; } }
///
///
/// for internal test only
///
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
Expand Down
Expand Up @@ -78,6 +78,9 @@
<Compile Include="..\..\src\Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs">
<Link>Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs</Link>
</Compile>
<Compile Include="..\..\src\Microsoft\Data\SqlClient\AzureManagedIdentityAuthenticationProvider.cs">
<Link>Microsoft\Data\SqlClient\AzureManagedIdentityAuthenticationProvider.cs</Link>
</Compile>
<Compile Include="..\..\src\Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs">
<Link>Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs</Link>
</Compile>
Expand Down
Expand Up @@ -104,10 +104,12 @@ internal static string ConvertToString(object value)
const string ActiveDirectoryInteractiveString = "Active Directory Interactive";
const string ActiveDirectoryServicePrincipalString = "Active Directory Service Principal";
const string ActiveDirectoryDeviceCodeFlowString = "Active Directory Device Code Flow";
internal const string ActiveDirectoryManagedIdentityString = "Active Directory Managed Identity";
internal const string ActiveDirectoryMSIString = "Active Directory MSI";

internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result)
{
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed");
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 9, "SqlAuthenticationMethod enum has changed, update needed");

bool isSuccess = false;

Expand Down Expand Up @@ -147,6 +149,18 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent
result = SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow;
isSuccess = true;
}
else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryManagedIdentityString)
|| StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, CultureInfo.InvariantCulture)))
{
result = SqlAuthenticationMethod.ActiveDirectoryManagedIdentity;
isSuccess = true;
}
else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryMSIString)
|| StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryMSI, CultureInfo.InvariantCulture)))
{
result = SqlAuthenticationMethod.ActiveDirectoryMSI;
isSuccess = true;
}
else
{
result = DbConnectionStringDefaults.Authentication;
Expand Down Expand Up @@ -367,7 +381,7 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st
}
catch (ArgumentException e)
{
// to be consistent with the messages we send in case of wrong type usage, replace
// to be consistent with the messages we send in case of wrong type usage, replace
// the error with our exception, and keep the original one as inner one for troubleshooting
throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionAttestationProtocol), e);
}
Expand Down Expand Up @@ -411,7 +425,7 @@ internal static string ApplicationIntentToString(ApplicationIntent value)
/// * if the value is from type ApplicationIntent, it will be used as is
/// * if the value is from integral type (SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64), it will be converted to enum
/// * if the value is another enum or any other type, it will be blocked with an appropriate ArgumentException
///
///
/// in any case above, if the converted value is out of valid range, the method raises ArgumentOutOfRangeException.
/// </summary>
/// <returns>application intent value in the valid range</returns>
Expand Down Expand Up @@ -467,7 +481,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj
}
catch (ArgumentException e)
{
// to be consistent with the messages we send in case of wrong type usage, replace
// to be consistent with the messages we send in case of wrong type usage, replace
// the error with our exception, and keep the original one as inner one for troubleshooting
throw ADP.ConvertFailed(value.GetType(), typeof(ApplicationIntent), e);
}
Expand All @@ -487,13 +501,15 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj

internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value)
{
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed");
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 9, "SqlAuthenticationMethod enum has changed, update needed");
return value == SqlAuthenticationMethod.SqlPassword
|| value == SqlAuthenticationMethod.ActiveDirectoryPassword
|| value == SqlAuthenticationMethod.ActiveDirectoryIntegrated
|| value == SqlAuthenticationMethod.ActiveDirectoryInteractive
|| value == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal
|| value == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow
|| value == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity
|| value == SqlAuthenticationMethod.ActiveDirectoryMSI
|| value == SqlAuthenticationMethod.NotSpecified;
}

Expand All @@ -515,6 +531,10 @@ internal static string AuthenticationTypeToString(SqlAuthenticationMethod value)
return ActiveDirectoryServicePrincipalString;
case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow:
return ActiveDirectoryDeviceCodeFlowString;
case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity:
return ActiveDirectoryManagedIdentityString;
case SqlAuthenticationMethod.ActiveDirectoryMSI:
return ActiveDirectoryMSIString;
default:
return null;
}
Expand Down Expand Up @@ -572,7 +592,7 @@ internal static SqlAuthenticationMethod ConvertToAuthenticationType(string keywo
}
catch (ArgumentException e)
{
// to be consistent with the messages we send in case of wrong type usage, replace
// to be consistent with the messages we send in case of wrong type usage, replace
// the error with our exception, and keep the original one as inner one for troubleshooting
throw ADP.ConvertFailed(value.GetType(), typeof(SqlAuthenticationMethod), e);
}
Expand Down Expand Up @@ -648,7 +668,7 @@ internal static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSe
}
catch (ArgumentException e)
{
// to be consistent with the messages we send in case of wrong type usage, replace
// to be consistent with the messages we send in case of wrong type usage, replace
// the error with our exception, and keep the original one as inner one for troubleshooting
throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionColumnEncryptionSetting), e);
}
Expand All @@ -672,40 +692,41 @@ internal static partial class DbConnectionStringDefaults
// all
// internal const string NamedConnection = "";

private const string _emptyString = "";
// SqlClient
internal const ApplicationIntent ApplicationIntent = Microsoft.Data.SqlClient.ApplicationIntent.ReadWrite;
internal const string ApplicationName = "Core Microsoft SqlClient Data Provider";
internal const string AttachDBFilename = "";
internal const string AttachDBFilename = _emptyString;
internal const int CommandTimeout = 30;
internal const int ConnectTimeout = 15;
internal const string CurrentLanguage = "";
internal const string DataSource = "";
internal const string CurrentLanguage = _emptyString;
internal const string DataSource = _emptyString;
internal const bool Encrypt = false;
internal const bool Enlist = true;
internal const string FailoverPartner = "";
internal const string InitialCatalog = "";
internal const string FailoverPartner = _emptyString;
internal const string InitialCatalog = _emptyString;
internal const bool IntegratedSecurity = false;
internal const int LoadBalanceTimeout = 0; // default of 0 means don't use
internal const bool MultipleActiveResultSets = false;
internal const bool MultiSubnetFailover = false;
internal const int MaxPoolSize = 100;
internal const int MinPoolSize = 0;
internal const int PacketSize = 8000;
internal const string Password = "";
internal const string Password = _emptyString;
internal const bool PersistSecurityInfo = false;
internal const bool Pooling = true;
internal const bool TrustServerCertificate = false;
internal const string TypeSystemVersion = "Latest";
internal const string UserID = "";
internal const string UserID = _emptyString;
internal const bool UserInstance = false;
internal const bool Replication = false;
internal const string WorkstationID = "";
internal const string WorkstationID = _emptyString;
internal const string TransactionBinding = "Implicit Unbind";
internal const int ConnectRetryCount = 1;
internal const int ConnectRetryInterval = 10;
internal static readonly SqlAuthenticationMethod Authentication = SqlAuthenticationMethod.NotSpecified;
internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = "";
internal const string EnclaveAttestationUrl = _emptyString;
David-Engel marked this conversation as resolved.
Show resolved Hide resolved
internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
}

Expand Down Expand Up @@ -813,7 +834,7 @@ internal static class DbConnectionStringSynonyms

//internal const string MultiSubnetFailover = MULTISUBNETFAILOVER;
internal const string MULTISUBNETFAILOVER = "MultiSubnetFailover";

//internal const string NetworkLibrary = NET+","+NETWORK;
internal const string NET = "net";
internal const string NETWORK = "network";
Expand Down
Expand Up @@ -15,6 +15,7 @@ internal partial class SqlAuthenticationProviderManager

static SqlAuthenticationProviderManager()
{
var azureManagedIdentityAuthenticationProvider = new AzureManagedIdentityAuthenticationProvider();
SqlAuthenticationProviderConfigurationSection configurationSection = null;

try
Expand All @@ -40,6 +41,8 @@ static SqlAuthenticationProviderManager()
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, azureManagedIdentityAuthenticationProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, azureManagedIdentityAuthenticationProvider);
}

/// <summary>
Expand Down Expand Up @@ -153,6 +156,10 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe
return SqlAuthenticationMethod.ActiveDirectoryServicePrincipal;
case ActiveDirectoryDeviceCodeFlow:
return SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow;
case ActiveDirectoryManagedIdentity:
return SqlAuthenticationMethod.ActiveDirectoryManagedIdentity;
case ActiveDirectoryMSI:
return SqlAuthenticationMethod.ActiveDirectoryMSI;
default:
throw SQL.UnsupportedAuthentication(authentication);
}
Expand Down
Expand Up @@ -8,13 +8,17 @@ internal partial class SqlAuthenticationProviderManager
{
static SqlAuthenticationProviderManager()
{
var azureManagedIdentityAuthenticationProvider = new AzureManagedIdentityAuthenticationProvider();

Instance = new SqlAuthenticationProviderManager();
var activeDirectoryAuthProvider = new ActiveDirectoryAuthenticationProvider(Instance._applicationClientId);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, azureManagedIdentityAuthenticationProvider);
Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, azureManagedIdentityAuthenticationProvider);
}
}
}
Expand Up @@ -19,6 +19,8 @@ internal partial class SqlAuthenticationProviderManager
private const string ActiveDirectoryInteractive = "active directory interactive";
private const string ActiveDirectoryServicePrincipal = "active directory service principal";
private const string ActiveDirectoryDeviceCodeFlow = "active directory device code flow";
private const string ActiveDirectoryManagedIdentity = "active directory managed identity";
private const string ActiveDirectoryMSI = "active directory msi";

private readonly string _typeName;
private readonly IReadOnlyCollection<SqlAuthenticationMethod> _authenticationsWithAppSpecifiedProvider;
Expand Down