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
{
Name = "columnEncryptionSetting",
DisplayName = "Column encryption setting",
Description = "Default column encryption setting for all the commands on the connection",
DisplayName = "Always Encrypted",
Description = "Enables or disables Always Encrypted for the connection",
ValueType = ConnectionOption.ValueTypeCategory,
GroupName = "Security",
CategoryValues = new CategoryValue[] {
new CategoryValue { Name = "Disabled" },
new CategoryValue {Name = "Enabled" }
new CategoryValue { Name = "Enabled" }
}
},
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",
DisplayName = "Encrypt",

View File

@@ -1156,6 +1156,34 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
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)
{
connectionBuilder.Encrypt = connectionDetails.Encrypt.Value;
@@ -1328,6 +1356,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
CurrentLanguage = builder.CurrentLanguage,
DatabaseName = builder.InitialCatalog,
ColumnEncryptionSetting = builder.ColumnEncryptionSetting.ToString(),
EnclaveAttestationProtocol = builder.AttestationProtocol.ToString(),
EnclaveAttestationUrl = builder.EnclaveAttestationUrl,
Encrypt = builder.Encrypt,
FailoverPartner = builder.FailoverPartner,
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>
/// 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>

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype=">text/microsoft-resx</resheader>
<resheader name="version=">2.0</resheader>
<resheader name="reader=">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer=">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1="><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing=">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64=">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64=">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype=">text/microsoft-resx</resheader>
<resheader name="version=">2.0</resheader>
<resheader name="reader=">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer=">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1="><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing=">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64=">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64=">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata=">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
@@ -145,6 +145,15 @@
<comment>.
Parameters: 0 - columnEncryptionSetting (string) </comment>
</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">
<value>Invalid value &apos;{0}&apos; for ApplicationIntent. Valid values are &apos;ReadWrite&apos; and &apos;ReadOnly&apos;.</value>
<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'.
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'.
ConnectionServiceConnectionCanceled = Connection canceled

View File

@@ -45,6 +45,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
Assert.Equal(details.MinPoolSize, expectedForInt);
Assert.Equal(details.PacketSize, expectedForInt);
Assert.Equal(details.ColumnEncryptionSetting, expectedForStrings);
Assert.Equal(details.EnclaveAttestationUrl, expectedForStrings);
Assert.Equal(details.EnclaveAttestationProtocol, expectedForStrings);
Assert.Equal(details.Encrypt, expectedForBoolean);
Assert.Equal(details.MultipleActiveResultSets, expectedForBoolean);
Assert.Equal(details.MultiSubnetFailover, expectedForBoolean);
@@ -83,6 +85,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
details.MinPoolSize = expectedForInt + index++;
details.PacketSize = expectedForInt + index++;
details.ColumnEncryptionSetting = expectedForStrings + index++;
details.EnclaveAttestationProtocol = expectedForStrings + index++;
details.EnclaveAttestationUrl = expectedForStrings + index++;
details.Encrypt = (index++ % 2 == 0);
details.MultipleActiveResultSets = (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.PacketSize, expectedForInt + 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.MultipleActiveResultSets, (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.PacketSize = expectedForInt + index++;
details.ColumnEncryptionSetting = expectedForStrings + index++;
details.EnclaveAttestationProtocol = expectedForStrings + index++;
details.EnclaveAttestationUrl = expectedForStrings + index++;
details.Encrypt = (index++ % 2 == 0);
details.MultipleActiveResultSets = (index++ % 2 == 0);
details.MultiSubnetFailover = (index++ % 2 == 0);

View File

@@ -19,6 +19,7 @@ using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
using Moq;
using Moq.Protected;
using Xunit;
using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
{
@@ -552,12 +553,45 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
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>
/// Build connection string with an invalid property type
/// </summary>
[Theory]
[InlineData("AuthenticationType", "NotAValidAuthType")]
[InlineData("ColumnEncryptionSetting", "NotAValidColumnEncryptionSetting")]
[InlineData("EnclaveAttestationProtocol", "NotAValidEnclaveAttestationProtocol")]
public void BuildConnectionStringWithInvalidOptions(string propertyName, object propertyValue)
{
ConnectionDetails details = TestObjects.GetTestConnectionDetails();
@@ -566,6 +600,59 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
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>
/// Verify that a connection changed event is fired when the database context changes.
/// </summary>