Insert data into "Always encrypted" Azure SQL DB table using powershell - c#

I have a Azure SQL DB with Always encrypted table and keys stored in Azure KeyVault. I wish to insert data into the "always encrypted" table using Powershell.
I understand the Invoke-sqlcmd does not support and I am using the SqlCommand.ExecuteNonQuery Method () to achieve the same
$connStr = "Server={0};Database={1};User ID={2};Password={3};Column Encryption Setting=enabled;" -f $SqlDBServerInstance, $SqlConnectionInfo.DatabaseName, $SqlConnectionInfo.UserName, $SqlConnectionInfo.Password
$sqlConn = New-Object System.Data.SqlClient.SqlConnection
$sqlConn.ConnectionString = $connStr
$sqlConn.Open()
$sqlcmd = New-Object System.Data.SqlClient.SqlCommand
$sqlcmd.Connection = $sqlConn
$sqlcmd.CommandText = "INSERT INTO dbo.SecureTable (Column1) VALUES (#Param1)"
$sqlcmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("#Param1", [Data.SQLDBType]::VarChar,50)))
$sqlcmd.Parameters["#Param1"].Value = "$Param1"
$sqlcmd.ExecuteNonQuery();
When I run this code I get below error even though I have right cryptographic access (Unwrap, Wrap, Verify, Sign) given to the service principal at key vault level in azure
Exception calling "ExecuteNonQuery" with "0" argument(s): "Failed to
decrypt a column encryption key. Invalid key store provider name:
'AZURE_KEY_VAULT'. A key store provider name must denote either a
system key store provider or a registered custom key store provider.
Valid system key store provider names are: 'MSSQL_CERTIFICATE_STORE',
'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Valid (currently registered)
custom key store provider names are: . Please verify key store
provider information in column master key definitions in the database,
and verify all custom key store providers used in your application are
registered properly."
The way described here does not work for me probably due to I am using Key Vault to store my keys.
To resolve this error, there is one step needed from C# to use InitializeAzureKeyVaultProvider but since I am trying to achieve this from Powershell and SqlServer PS module only, is there any way I can achieve this using Powershell only? Without going the C# way explained here

Related

How to resolve this error: "Encryption scheme mismatch for columns/variables" for Always Encrypted feature enabled in Entity Framework

I am getting below exception when I try to run the application from visual studio 2019.
I am fetching data through Entity framework form SQL server 2019. I have made all necessary configurations to enable always encrypted in respective DB and modified connection string with column encryption setting = Enabled
Inner Exception: System.Data.SqlClient.SqlException (0x80131904): Encryption scheme mismatch for columns/variables 'Column Name'. The encryption scheme for the columns/variables is (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'AEForCEK', column_encryption_key_database_name = 'DBName') and the expression near line '14' expects it to be PLAINTEXT
As per the sources I have enabled parameterization in SSMS query window and run below simple query, I am able to get the result as expected. but how to enable this when I am getting data through entity framework in my C# Application.
declare #email nvarchar(100) = 'head#headoffice.com'
select * FROM [DBNAme].[dbo].[TableName] where email= #email
In the above query email column is encrypted with deterministic type
Without proper code details, it's quite tough to understand. Though, try this
using (var context = new YourDbContext())
{
context.Database.CommandTimeout = 0;
// Your query logic
}

Add 2fa authenticator to user

I have been trying to work out how to enable 2f login with Google Authentication in my Identity server 4 application.
2fa works fine with both email and phone.
if i check
var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
it has two email and phone. I am assuming that this would be the two factor providers that have been set up for this user.
Now if i check _usermanager again there is a field called tokenproviders. Which appears to contain default, email, phone, and authenticator. I assume these are the ones that Asp .net identity is configured to deal with.
I have worked out how to create the secret needed to genreate the QR code for the authecator app. As well has how to build the QR code and to test the code
var code = _userManager.GenerateNewAuthenticatorKey();
var qr = AuthencatorHelper.GetQrCodeGoogleUrl("bob", code, "My Company");
var user = await _signInManager.TwoFactorAuthenticatorSignInAsync(codeFromAppToTestWith, true, false);
if (user == null)
{
return View("Error");
}
Now the problem. I have gone though every method I can find on the user trying to work out how to add another token provider to the user.
How do I assign a new token provider to the user and supply the secret code needed to create the authentication codes?? I am not even seeing any tables in the database setup to handle this information. email and phone number are there and there is a column for 2faenabled. But nothing about authenticator.
I am currently looking into creating a custom usermanager and adding a field onto the application user. I was really hoping someone had a better idea.
From what I can see, you are generating a new authenticator key each time the user needs to configure an authenticator app:
var code = _userManager.GenerateNewAuthenticatorKey();
You should be aware that using GenerateNewAuthenticatorCodeAsync will not persist the key, and thus will not be useful for 2FA.
Instead, you need to generate and persist the key in the underlying storage, if it not already created:
var key = await _userManager.GetAuthenticatorKeyAsync(user); // get the key
if (string.IsNullOrEmpty(key))
{
// if no key exists, generate one and persist it
await _userManager.ResetAuthenticatorKeyAsync(user);
// get the key we just created
key = await _userManager.GetAuthenticatorKeyAsync(user);
}
Which will generate the key if not already done and persist it in the database (or any storage configured for Identity).
Without persisting the key inside the storage, the AuthenticatorTokenProvider will never be able to generate tokens, and will not be available when calling GetValidTwoFactorProvidersAsync.

How to get CngKey in web application?

I want to access a named key in an ASP.NET MVC application but i am not able to access it.
I create a key using powershell. please find below the code for that.
#Create Cng Key Parameter and set its properties
[System.Security.Cryptography.CngKeyCreationParameters] $cngKeyParameter = [System.Security.Cryptography.CngKeyCreationParameters]::new()
$cngKeyParameter.KeyUsage = [System.Security.Cryptography.CngKeyUsages]::AllUsages
$cngKeyParameter.ExportPolicy = [System.Security.Cryptography.CngExportPolicies]::AllowPlaintextExport
$cngKeyParameter.Provider = [System.Security.Cryptography.CngProvider]::MicrosoftSoftwareKeyStorageProvider
$cngKeyParameter.UIPolicy = [System.Security.Cryptography.CngUIPolicy]::new([System.Security.Cryptography.CngUIProtectionLevels]::None)
$cngKeyParameter.KeyCreationOptions = [System.Security.Cryptography.CngKeyCreationOptions]::MachineKey
#Create Cng Property for Length, set its value and add it to Cng Key Parameter
[System.Security.Cryptography.CngProperty] $cngProperty = [System.Security.Cryptography.CngProperty]::new($cngPropertyName, [System.BitConverter]::GetBytes(2048), [System.Security.Cryptography.CngPropertyOptions]::None)
$cngKeyParameter.Parameters.Add($cngProperty)
#Create Cng Key for given $keyName using Rsa Algorithm
[System.Security.Cryptography.CngKey] $key = [System.Security.Cryptography.CngKey]::Create([System.Security.Cryptography.CngAlgorithm]::Rsa, "ExampleKeyName", $cngKeyParameter)
Write-Output "CNG Key : ExampleKeyName - Created"
The key gets created successfully but the same key is not getting accessed in web application using below code.
CngKey.Exists("ExampleKeyName")
Can anyone help?
CngKey.Exists("ExampleKeyName") tries to find user-level key, and your powershell script creates machine-level key. So you need to check with:
CngKey.Exists(
"ExampleKeyName",
CngProvider.MicrosoftSoftwareKeyStorageProvider,
CngKeyOpenOptions.MachineKey
);

Key store providers cannot be set more than once (Always Encryption using Azure Function )

I've implemented a TimeTriggerC# Azure Function to Run a Store Procedure to an Insert a table that is Encrypted using Column Encryption(Always Encrypted, AzureKey Vault As Provider)
The Blocking area for me is that my Function ran successfully at 1 time and got an error for again many times. So I get the success path as a rare case
The Error I faced is
mscorlib: Exception has been thrown by the target of an invocation.
System.Data: Key store providers cannot be set more than once.
I done some investigation on this It occurs in the line
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
With in the function
static void InitializeAzureKeyVaultProvider()
{
string clientId = ConfigurationManager.AppSettings["AuthClientId"];
string clientSecret = ConfigurationManager.AppSettings["AuthClientSecret"];
_clientCredential = new ClientCredential(clientId, clientSecret);
// Direct the provider to the authentication delegate
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(GetToken);
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
providers.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
// register the provider with ADO.net
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
}
When I tried to look much deeper into the root cause of the error, it will be like
Exception has been thrown by the target of an invocation. --->
System.InvalidOperationException : Key store providers cannot be set
more than once. at
System.Data.SqlClient.SqlConnection.RegisterColumnEncryptionKeyStoreProviders(IDictionary`2
customProviders)
As I mentioned above I used Azure Key Vault as my Key Store Provider.I properly registered my Keyvault with AD and added the registered application to my key vault (Access Policies).
Why I'm facing this abnormal behavior, like one-time success and another time failure?
Appreciate your Response,
Jayendran
You are probably running InitializeAzureKeyVaultProvider() inside every function call. If works for the first time, when initialization wasn't performed on this App instance.
During the next function call the instance gets reused, so the initialization is already performed. But you call it again, thus you get duplicated registration error.
To solve it, do the initialization in a static constructor, or store a flag (static bool) whether you initialized already, and skip initialization if it's true (and take care of thread safety).
I experienced the same issue with an Azure Function triggered from messages coming into an Event Hub.
My solution is probably not 100% correct, but I'm unsure if the registration to the provider would be impacted after the token (from GetToken) expires. So I don't register until the first time I call an SQL query that needs the Azure Key Vault / Always Encrypted.
try
{
cmd.ExecuteNonQuery();
}
catch (System.ArgumentException ex)
{
if (ex.TargetSite.Name == "DecryptSymmetricKey" && ex.InnerException == null)
{
InitializeAzureKeyVaultProvider() //Register the Provider
cmd.ExecuteNonQuery(); // Try the query again.
}
}

SqlDependency not updating cache

I'm on a project using .NET 4.5, MVC, EF 6
I had naively implemented a caching system using the HttpRuntime cache and needed to invalidate the data I cache on updates to that data; except I forgot to take into account that our production server is published to a load balanced set of two servers... :|
So on production, after the data was updated, the app would sometimes serve the right data, and sometimes the old data depending on which server the request was hitting. Bad news bears.
So I decided to define a dependency on the SQL table AcademicTerms which is where my data is coming from. But I did something wrong, and I'm not sure what.
SQL that I ran to set up the permissions after enabling the Service Broker
EXEC sp_addrole 'sql_dependency_role'
GRANT CREATE PROCEDURE to sql_dependency_role
GRANT CREATE QUEUE to sql_dependency_role
GRANT CREATE SERVICE to sql_dependency_role
GRANT REFERENCES on
CONTRACT::[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification]
to sql_dependency_role
GRANT VIEW DEFINITION TO sql_dependency_role
GRANT SELECT to sql_dependency_role
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO sql_dependency_role
GRANT RECEIVE ON QueryNotificationErrorsQueue TO sql_dependency_role
EXEC sp_addrolemember 'sql_dependency_role', 'MY_ASPNET_APP_USERNAME'
My implementation of inserting new data after fetching and thus setting up the SqlDependency (hopefully less naive!):
private void insertIntoCache(
AcademicTermLockingInfo newItem,
string itemKey,
Guid termID) {
var dbContextConnection = db.Database.Connection;
var connectionString = dbContextConnection.ConnectionString;
// important step otherwise it won't work
SqlDependency.Start(connectionString);
CacheItemPolicy policy = new CacheItemPolicy {
AbsoluteExpiration = DateTime.UtcNow.AddMonths(6)
};
CacheItem item = new CacheItem(itemKey, newItem);
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
// command which will be used to notify updates - probably want to parametrize this
using (SqlCommand command =
new SqlCommand(
String.Format("SELECT Name, LockDate FROM dbo.AcademicTerms WHERE ID = '{0}'",
termID),
connection)) {
SqlDependency dependency = new SqlDependency(command);
SqlChangeMonitor monitor = new SqlChangeMonitor(dependency);
policy.ChangeMonitors.Add(monitor);
MemoryCache.Default.Set(item, policy);
// execute once otherwise dependency not registered
command.ExecuteNonQuery();
}
}
}
Any help would be very much appreciated!
Things I've done:
Created two new users in SQL Server, net and sanba
Added every NT* login and the sa login to the net user, added net to the sql_dependency_role
Ran grant alter on schema::sql_dependency_role to net and grant alter on schema::dbo to net
Check that my local SQL Server's Broker Enabled option is True under Service Broker
Tried the web cache and the Memory Cache interchangeably (probably wouldn't change anything)
Tried making the sql command string have a fully qualified name DevUMS.dbo.AcademicTerms and dbo.AcademicTerms
I queried the sys.dm_qn_subscriptions and saw I had one subscription, good!
I queried DevUMS.sys.transmission_queue and found an excpetion!
An exception occurred while enqueueing a message in the target
queue. Error: 15517, State: 1. Cannot execute as the database
principal because the principal "dbo" does not exist, this type of
principal cannot be impersonated, or you do not have permission.
I found this SO post with the same error
The secret sauce I was missing was alter authorization on database::DevUMS to [sa]; which I found on the linked SO post's answer.
There are a number of other steps, like adding a Role to use the appropriate login, but honestly, I'm really unsure as to whether or not those are actually necessary.
I'm going to publish a little later on today, and then I'll try to do the minimal amount of steps and document that here. I found the documentation in the wild to be very scattered and poor, so I hope to have this answer be a definitive place to refer to in the future

Categories