Enable Always Encrypted enclave connection parameters (#919)

* Enable the enclave connection parameters.

* Update the switch statement to use the enum constants for EnclaveAttestationProtocol

* Update verbiage for Always Encrypted connection options

* Update the argument exception to chose one specific to this connection option

* Add resource logic to resource files.

* Add error checking for when enclave parameters are added and Always Encrypted is set to disabled.

* Add/Update unit tests
This commit is contained in:
Jeff Trimmer
2020-02-18 15:33:12 -08:00
committed by GitHub
parent 7b102df5a7
commit 927b0d73ca
9 changed files with 1875 additions and 1667 deletions

View File

@@ -119,16 +119,36 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
new ConnectionOption new ConnectionOption
{ {
Name = "columnEncryptionSetting", Name = "columnEncryptionSetting",
DisplayName = "Column encryption setting", DisplayName = "Always Encrypted",
Description = "Default column encryption setting for all the commands on the connection", Description = "Enables or disables Always Encrypted for the connection",
ValueType = ConnectionOption.ValueTypeCategory, ValueType = ConnectionOption.ValueTypeCategory,
GroupName = "Security", GroupName = "Security",
CategoryValues = new CategoryValue[] { CategoryValues = new CategoryValue[] {
new CategoryValue { Name = "Disabled" }, new CategoryValue { Name = "Disabled" },
new CategoryValue {Name = "Enabled" } new CategoryValue { Name = "Enabled" }
} }
}, },
new ConnectionOption new ConnectionOption
{
Name = "attestationProtocol",
DisplayName = "Attestation Protocol",
Description = "Specifies a protocol for attesting a server-side enclave used with Always Encrypted with secure enclaves",
ValueType = ConnectionOption.ValueTypeCategory,
GroupName = "Security",
CategoryValues = new CategoryValue[] {
new CategoryValue { DisplayName = "Host Guardian Service", Name = "HGS" },
new CategoryValue { DisplayName = "Azure Attestation", Name = "AAS" }
}
},
new ConnectionOption
{
Name = "enclaveAttestationUrl",
DisplayName = "Enclave Attestation URL",
Description = "Specifies an endpoint for attesting a server-side enclave used with Always Encrypted with secure enclaves",
ValueType = ConnectionOption.ValueTypeString,
GroupName = "Security"
},
new ConnectionOption
{ {
Name = "encrypt", Name = "encrypt",
DisplayName = "Encrypt", DisplayName = "Encrypt",

View File

@@ -1156,6 +1156,34 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
throw new ArgumentException(SR.ConnectionServiceConnStringInvalidColumnEncryptionSetting(connectionDetails.ColumnEncryptionSetting)); throw new ArgumentException(SR.ConnectionServiceConnStringInvalidColumnEncryptionSetting(connectionDetails.ColumnEncryptionSetting));
} }
} }
if (!string.IsNullOrEmpty(connectionDetails.EnclaveAttestationProtocol))
{
if (string.IsNullOrEmpty(connectionDetails.ColumnEncryptionSetting) || connectionDetails.ColumnEncryptionSetting.ToUpper() == "DISABLED")
{
throw new ArgumentException(SR.ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination());
}
switch (connectionDetails.EnclaveAttestationProtocol.ToUpper())
{
case "AAS":
connectionBuilder.AttestationProtocol = SqlConnectionAttestationProtocol.AAS;
break;
case "HGS":
connectionBuilder.AttestationProtocol = SqlConnectionAttestationProtocol.HGS;
break;
default:
throw new ArgumentException(SR.ConnectionServiceConnStringInvalidEnclaveAttestationProtocol(connectionDetails.EnclaveAttestationProtocol));
}
}
if (!string.IsNullOrEmpty(connectionDetails.EnclaveAttestationUrl))
{
if (string.IsNullOrEmpty(connectionDetails.ColumnEncryptionSetting) || connectionDetails.ColumnEncryptionSetting.ToUpper() == "DISABLED")
{
throw new ArgumentException(SR.ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination());
}
connectionBuilder.EnclaveAttestationUrl = connectionDetails.EnclaveAttestationUrl;
}
if (connectionDetails.Encrypt.HasValue) if (connectionDetails.Encrypt.HasValue)
{ {
connectionBuilder.Encrypt = connectionDetails.Encrypt.Value; connectionBuilder.Encrypt = connectionDetails.Encrypt.Value;
@@ -1328,6 +1356,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
CurrentLanguage = builder.CurrentLanguage, CurrentLanguage = builder.CurrentLanguage,
DatabaseName = builder.InitialCatalog, DatabaseName = builder.InitialCatalog,
ColumnEncryptionSetting = builder.ColumnEncryptionSetting.ToString(), ColumnEncryptionSetting = builder.ColumnEncryptionSetting.ToString(),
EnclaveAttestationProtocol = builder.AttestationProtocol.ToString(),
EnclaveAttestationUrl = builder.EnclaveAttestationUrl,
Encrypt = builder.Encrypt, Encrypt = builder.Encrypt,
FailoverPartner = builder.FailoverPartner, FailoverPartner = builder.FailoverPartner,
LoadBalanceTimeout = builder.LoadBalanceTimeout, LoadBalanceTimeout = builder.LoadBalanceTimeout,

View File

@@ -115,6 +115,38 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
} }
} }
/// <summary>
/// Gets or sets a value for Attestation Protocol.
/// </summary>
public string EnclaveAttestationProtocol
{
get
{
return GetOptionValue<string>("attestationProtocol");
}
set
{
SetOptionValue("attestationProtocol", value);
}
}
/// <summary>
/// Gets or sets the enclave attestation Url to be used with enclave based Always Encrypted.
/// </summary>
public string EnclaveAttestationUrl
{
get
{
return GetOptionValue<string>("enclaveAttestationUrl");
}
set
{
SetOptionValue("enclaveAttestationUrl", value);
}
}
/// <summary> /// <summary>
/// Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. /// Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.
/// </summary> /// </summary>

View File

@@ -23,6 +23,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
Password = details.Password, Password = details.Password,
AuthenticationType = details.AuthenticationType, AuthenticationType = details.AuthenticationType,
ColumnEncryptionSetting = details.ColumnEncryptionSetting, ColumnEncryptionSetting = details.ColumnEncryptionSetting,
EnclaveAttestationProtocol = details.EnclaveAttestationProtocol,
EnclaveAttestationUrl = details.EnclaveAttestationUrl,
Encrypt = details.Encrypt, Encrypt = details.Encrypt,
TrustServerCertificate = details.TrustServerCertificate, TrustServerCertificate = details.TrustServerCertificate,
PersistSecurityInfo = details.PersistSecurityInfo, PersistSecurityInfo = details.PersistSecurityInfo,

View File

@@ -2992,6 +2992,16 @@ namespace Microsoft.SqlTools.ServiceLayer
return Keys.GetString(Keys.ConnectionServiceConnStringInvalidColumnEncryptionSetting, columnEncryptionSetting); return Keys.GetString(Keys.ConnectionServiceConnStringInvalidColumnEncryptionSetting, columnEncryptionSetting);
} }
public static string ConnectionServiceConnStringInvalidEnclaveAttestationProtocol(string enclaveAttestationProtocol)
{
return Keys.GetString(Keys.ConnectionServiceConnStringInvalidEnclaveAttestationProtocol, enclaveAttestationProtocol);
}
public static string ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination()
{
return Keys.GetString(Keys.ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination);
}
public static string ConnectionServiceConnStringInvalidIntent(string intent) public static string ConnectionServiceConnStringInvalidIntent(string intent)
{ {
return Keys.GetString(Keys.ConnectionServiceConnStringInvalidIntent, intent); return Keys.GetString(Keys.ConnectionServiceConnStringInvalidIntent, intent);
@@ -3168,6 +3178,12 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string ConnectionServiceConnStringInvalidColumnEncryptionSetting = "ConnectionServiceConnStringInvalidColumnEncryptionSetting"; public const string ConnectionServiceConnStringInvalidColumnEncryptionSetting = "ConnectionServiceConnStringInvalidColumnEncryptionSetting";
public const string ConnectionServiceConnStringInvalidEnclaveAttestationProtocol = "ConnectionServiceConnStringInvalidEnclaveAttestationProtocol";
public const string ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination = "ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination";
public const string ConnectionServiceConnStringInvalidIntent = "ConnectionServiceConnStringInvalidIntent"; public const string ConnectionServiceConnStringInvalidIntent = "ConnectionServiceConnStringInvalidIntent";

View File

@@ -145,6 +145,15 @@
<comment>. <comment>.
Parameters: 0 - columnEncryptionSetting (string) </comment> Parameters: 0 - columnEncryptionSetting (string) </comment>
</data> </data>
<data name="ConnectionServiceConnStringInvalidEnclaveAttestationProtocol" xml:space="preserve">
<value>Invalid value &apos;{0}&apos; for EnclaveAttestationProtocol. Valid values are &apos;AAS&apos; and &apos;HGS&apos;.</value>
<comment>.
Parameters: 0 - enclaveAttestationProtocol (string) </comment>
</data>
<data name="ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination" xml:space="preserve">
<value>The Attestation Protocol and Enclave Attestation URL requires Always Encrypted to be set to Enabled.</value>
<comment></comment>
</data>
<data name="ConnectionServiceConnStringInvalidIntent" xml:space="preserve"> <data name="ConnectionServiceConnStringInvalidIntent" xml:space="preserve">
<value>Invalid value &apos;{0}&apos; for ApplicationIntent. Valid values are &apos;ReadWrite&apos; and &apos;ReadOnly&apos;.</value> <value>Invalid value &apos;{0}&apos; for ApplicationIntent. Valid values are &apos;ReadWrite&apos; and &apos;ReadOnly&apos;.</value>
<comment>. <comment>.

View File

@@ -35,6 +35,10 @@ ConnectionServiceConnStringInvalidAuthType(string authType) = Invalid value '{0}
ConnectionServiceConnStringInvalidColumnEncryptionSetting(string columnEncryptionSetting) = Invalid value '{0}' for ComlumEncryption. Valid values are 'Enabled' and 'Disabled'. ConnectionServiceConnStringInvalidColumnEncryptionSetting(string columnEncryptionSetting) = Invalid value '{0}' for ComlumEncryption. Valid values are 'Enabled' and 'Disabled'.
ConnectionServiceConnStringInvalidEnclaveAttestationProtocol(string enclaveAttestationProtocol) = Invalid value '{0}' for EnclaveAttestationProtocol. Valid values are 'AAS' and 'HGS'.
ConnectionServiceConnStringInvalidAlwaysEncryptedOptionCombination = The Attestation Protocol and Enclave Attestation URL requires Always Encrypted to be set to Enabled.
ConnectionServiceConnStringInvalidIntent(string intent) = Invalid value '{0}' for ApplicationIntent. Valid values are 'ReadWrite' and 'ReadOnly'. ConnectionServiceConnStringInvalidIntent(string intent) = Invalid value '{0}' for ApplicationIntent. Valid values are 'ReadWrite' and 'ReadOnly'.
ConnectionServiceConnectionCanceled = Connection canceled ConnectionServiceConnectionCanceled = Connection canceled

View File

@@ -45,6 +45,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
Assert.Equal(details.MinPoolSize, expectedForInt); Assert.Equal(details.MinPoolSize, expectedForInt);
Assert.Equal(details.PacketSize, expectedForInt); Assert.Equal(details.PacketSize, expectedForInt);
Assert.Equal(details.ColumnEncryptionSetting, expectedForStrings); Assert.Equal(details.ColumnEncryptionSetting, expectedForStrings);
Assert.Equal(details.EnclaveAttestationUrl, expectedForStrings);
Assert.Equal(details.EnclaveAttestationProtocol, expectedForStrings);
Assert.Equal(details.Encrypt, expectedForBoolean); Assert.Equal(details.Encrypt, expectedForBoolean);
Assert.Equal(details.MultipleActiveResultSets, expectedForBoolean); Assert.Equal(details.MultipleActiveResultSets, expectedForBoolean);
Assert.Equal(details.MultiSubnetFailover, expectedForBoolean); Assert.Equal(details.MultiSubnetFailover, expectedForBoolean);
@@ -83,6 +85,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
details.MinPoolSize = expectedForInt + index++; details.MinPoolSize = expectedForInt + index++;
details.PacketSize = expectedForInt + index++; details.PacketSize = expectedForInt + index++;
details.ColumnEncryptionSetting = expectedForStrings + index++; details.ColumnEncryptionSetting = expectedForStrings + index++;
details.EnclaveAttestationProtocol = expectedForStrings + index++;
details.EnclaveAttestationUrl = expectedForStrings + index++;
details.Encrypt = (index++ % 2 == 0); details.Encrypt = (index++ % 2 == 0);
details.MultipleActiveResultSets = (index++ % 2 == 0); details.MultipleActiveResultSets = (index++ % 2 == 0);
details.MultiSubnetFailover = (index++ % 2 == 0); details.MultiSubnetFailover = (index++ % 2 == 0);
@@ -113,6 +117,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
Assert.Equal(details.MinPoolSize, expectedForInt + index++); Assert.Equal(details.MinPoolSize, expectedForInt + index++);
Assert.Equal(details.PacketSize, expectedForInt + index++); Assert.Equal(details.PacketSize, expectedForInt + index++);
Assert.Equal(details.ColumnEncryptionSetting, expectedForStrings + index++); Assert.Equal(details.ColumnEncryptionSetting, expectedForStrings + index++);
Assert.Equal(details.EnclaveAttestationProtocol, expectedForStrings + index++);
Assert.Equal(details.EnclaveAttestationUrl, expectedForStrings + index++);
Assert.Equal(details.Encrypt, (index++ % 2 == 0)); Assert.Equal(details.Encrypt, (index++ % 2 == 0));
Assert.Equal(details.MultipleActiveResultSets, (index++ % 2 == 0)); Assert.Equal(details.MultipleActiveResultSets, (index++ % 2 == 0));
Assert.Equal(details.MultiSubnetFailover, (index++ % 2 == 0)); Assert.Equal(details.MultiSubnetFailover, (index++ % 2 == 0));
@@ -152,6 +158,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
details.MinPoolSize = expectedForInt + index++; details.MinPoolSize = expectedForInt + index++;
details.PacketSize = expectedForInt + index++; details.PacketSize = expectedForInt + index++;
details.ColumnEncryptionSetting = expectedForStrings + index++; details.ColumnEncryptionSetting = expectedForStrings + index++;
details.EnclaveAttestationProtocol = expectedForStrings + index++;
details.EnclaveAttestationUrl = expectedForStrings + index++;
details.Encrypt = (index++ % 2 == 0); details.Encrypt = (index++ % 2 == 0);
details.MultipleActiveResultSets = (index++ % 2 == 0); details.MultipleActiveResultSets = (index++ % 2 == 0);
details.MultiSubnetFailover = (index++ % 2 == 0); details.MultiSubnetFailover = (index++ % 2 == 0);

View File

@@ -19,6 +19,7 @@ using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
using Moq; using Moq;
using Moq.Protected; using Moq.Protected;
using Xunit; using Xunit;
using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
{ {
@@ -552,12 +553,45 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
Assert.True(connectionString.Contains(connectionStringMarker)); Assert.True(connectionString.Contains(connectionStringMarker));
} }
/// <summary>
/// Verify that optional parameters which require ColumnEncryptionSetting to be enabled
/// can be built into a connection string for connecting.
/// </summary>
[Theory]
[InlineData("EnclaveAttestationProtocol", "AAS", "Attestation Protocol=AAS")]
[InlineData("EnclaveAttestationProtocol", "HGS", "Attestation Protocol=HGS")]
[InlineData("EnclaveAttestationProtocol", "aas", "Attestation Protocol=AAS")]
[InlineData("EnclaveAttestationProtocol", "hgs", "Attestation Protocol=HGS")]
[InlineData("EnclaveAttestationProtocol", "AaS", "Attestation Protocol=AAS")]
[InlineData("EnclaveAttestationProtocol", "hGs", "Attestation Protocol=HGS")]
[InlineData("EnclaveAttestationUrl", "https://attestation.us.attest.azure.net/attest/SgxEnclave", "Enclave Attestation Url=https://attestation.us.attest.azure.net/attest/SgxEnclave")]
public void ConnectingWithOptionalEnclaveParametersBuildsConnectionString(string propertyName, object propertyValue, string connectionStringMarker)
{
// Create a test connection details object and set the property to a specific value
ConnectionDetails details = TestObjects.GetTestConnectionDetails();
details.GetType()
.GetProperty("ColumnEncryptionSetting")
.SetValue(details, "Enabled");
details.GetType()
.GetProperty(propertyName)
.SetValue(details, propertyValue);
// Test that a connection string can be created without exceptions
string connectionString = ConnectionService.BuildConnectionString(details);
Assert.NotNull(connectionString);
Assert.NotEmpty(connectionString);
// Verify that the parameter is in the connection string
Assert.True(connectionString.Contains(connectionStringMarker));
}
/// <summary> /// <summary>
/// Build connection string with an invalid property type /// Build connection string with an invalid property type
/// </summary> /// </summary>
[Theory] [Theory]
[InlineData("AuthenticationType", "NotAValidAuthType")] [InlineData("AuthenticationType", "NotAValidAuthType")]
[InlineData("ColumnEncryptionSetting", "NotAValidColumnEncryptionSetting")] [InlineData("ColumnEncryptionSetting", "NotAValidColumnEncryptionSetting")]
[InlineData("EnclaveAttestationProtocol", "NotAValidEnclaveAttestationProtocol")]
public void BuildConnectionStringWithInvalidOptions(string propertyName, object propertyValue) public void BuildConnectionStringWithInvalidOptions(string propertyName, object propertyValue)
{ {
ConnectionDetails details = TestObjects.GetTestConnectionDetails(); ConnectionDetails details = TestObjects.GetTestConnectionDetails();
@@ -566,6 +600,59 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
Assert.Throws<ArgumentException>(() => ConnectionService.BuildConnectionString(details)); Assert.Throws<ArgumentException>(() => ConnectionService.BuildConnectionString(details));
} }
/// <summary>
/// Parameters used for test: BuildConnectionStringWithInvalidOptionCombinations
/// </summary>
public static readonly object[][] ConnectionStringWithInvalidOptionCombinations =
{
new object[]
{
typeof(ArgumentException),
new []
{
Tuple.Create<string, object>("ColumnEncryptionSetting", null),
Tuple.Create<string, object>("EnclaveAttestationProtocol", "AAS"),
Tuple.Create<string, object>("EnclaveAttestationUrl", "https://attestation.us.attest.azure.net/attest/SgxEnclave")
}
},
new object[]
{
typeof(ArgumentException),
new []
{
Tuple.Create<string, object>("ColumnEncryptionSetting", "Disabled"),
Tuple.Create<string, object>("EnclaveAttestationProtocol", "AAS"),
Tuple.Create<string, object>("EnclaveAttestationUrl", "https://attestation.us.attest.azure.net/attest/SgxEnclave")
}
},
new object[]
{
typeof(ArgumentException),
new []
{
Tuple.Create<string, object>("ColumnEncryptionSetting", ""),
Tuple.Create<string, object>("EnclaveAttestationProtocol", "AAS"),
Tuple.Create<string, object>("EnclaveAttestationUrl", "https://attestation.us.attest.azure.net/attest/SgxEnclave")
}
}
};
/// <summary>
/// Build connection string with an invalid property combinations
/// </summary>
[Theory]
[MemberData(nameof(ConnectionStringWithInvalidOptionCombinations))]
public void BuildConnectionStringWithInvalidOptionCombinations(Type exceptionType, Tuple<string, object>[] propertyNameValuePairs)
{
ConnectionDetails details = TestObjects.GetTestConnectionDetails();
propertyNameValuePairs.ToList().ForEach(tuple =>
{
PropertyInfo info = details.GetType().GetProperty(tuple.Item1);
info.SetValue(details, tuple.Item2);
});
Assert.Throws(exceptionType, () => ConnectionService.BuildConnectionString(details));
}
/// <summary> /// <summary>
/// Verify that a connection changed event is fired when the database context changes. /// Verify that a connection changed event is fired when the database context changes.
/// </summary> /// </summary>