Merge pull request #930 from Pietervanhove/AESQLDBWithEnclavesDemo

Merging revamped demo for AE with enclaves
This commit is contained in:
Pedro Lopes
2021-07-05 17:12:01 -07:00
committed by GitHub
1259 changed files with 253869 additions and 31764 deletions
@@ -1,151 +1,8 @@
# Always Encrypted with secure enclaves
:::image type="content" source="../../../manage/sql-server-extended-security-updates/media/solutions-microsoft-logo-small.png" alt-text="solutions-microsoft-logo-small":::
This sample/demo showcases the benefits of [Always Encrypted with secure enclaves](https://aka.ms/AlwaysEncryptedwithSecureEnclaves).
# Demos of Always Encrypted with secure enclaves
## About this sample
- **Applies to:** SQL Server 2019 CTP 2.1
- **Programming Language:** .NET C#, T-SQL
- **Authors:** Jakub Szymaszek [jaszymas-MSFT]
This set of samples/demos showcases [Always Encrypted with secure enclaves](https://docs.microsoft.com/azure/azure-sql/database/always-encrypted-with-secure-enclaves-landing).
This project has adopted the [Microsoft Open Source Code of Conduct](http://microsoft.github.io/codeofconduct). For more information see the [Code of Conduct FAQ](http://microsoft.github.io/codeofconduct/faq.md) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Contents
1. [Prerequisites](#prerequisites)
2. [Setup](#setup)
3. [Demo Part 1 - Tour the Clinic database and the demo application](#demo-part-1---tour-the-clinic-database-and-the-demo-application)
4. [Demo Part 2 - Encrypt columns in place](#demo-part-2---encrypt-columns-in-place)
5. [Demo Part 3 - Run rich queries from SSMS](#Demo-Part-3---run-rich-queries-from-SSMS)
6. [Demo Part 4 - Re-encrypt and decrypt columns in-place](#demo-part-4---re-encrypt-and-decrypt-columns-in-place)
7. [Resetting your demo environment](#resetting-your-demo-environment)
## Prerequisites
You need two machines (they can be virtual machines):
- The SQL Server computer:
+ Windows Server 2019 Datacenter or Windows 10 Enterprise version 1809
+ SQL Server 2019 CTP 2.1 or later
+ [SQL Server Management Studio 18.0 or later](https://msdn.microsoft.com/en-us/library/mt238290.aspx)
+ Visual Studio 2015 (or newer)
+ If this machine is a virtual machine, it must be a generation 2 VM.
- The HGS computer to host Windows Host Guardian Service for enclave attestation:
+ Windows Server 2019 Datacenter or Standard
## Setup
1. Configure host attestation for the SQL Server computer, following Steps 1-2 in [Tutorial: Getting started with Always Encrypted with secure enclaves using SSMS](https://aka.ms/AlwaysEncryptedEnclavesTutorial).
1. Enable Always Encrypted with secure enclaves in your SQL Server instances by following instructions in Step 2 in [Tutorial: Getting started with Always Encrypted with secure enclaves using SSMS](https://aka.ms/AlwaysEncryptedEnclavesTutorial).
1. Set up the Clinic demo database.
+ Clone/Download the repository.
+ Open SSMS and connect to your SQL Server 2019 instance.
+ In SSMS, right-click on **Databases** in Object Explorer and select **Import Data-tier Application...**.
+ Locate the **Clinic** bacpac file the **/setup** folder.
![Import Data-tier Application Wizard](img/import-bacpac.png)
+ Complete the steps of the wizard to import the **Clinic** database.
1. Set up the database connection string in the demo application.
+ Start Visual Studio and open the **ContosoClinic** solution file- located in **/src**.
+ Using Solution Explorer, locate and open the **web.config** file under the **ContosoClinic** project.
+ Look for the line that looks like this:
```csharp
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting = Enabled; Enclave Attestation URL=http://YourHGSComputer/Attestation"
</connectionStrings>
```
+ Make sure the value of the **Data Source** key word in the database connection string is correct (identifies your instance).
+ Make sure the **Initial Catalog** value is set to **Clinic**
+ Replace **YourHGSComputer** with a DNS name or an IP address of your HGS computer.
+ Save the file.
1. Build the demo application in Visual Studio.
+ Right click on your project in Solution Explorer and select **Properties**.
+ Make sure the .NET Framework 4.7.2 or higher is configured as the target .NET Framework for your project (change it, if necessary).
![Contoso Clinic Application Properties .Net Framework Version Setting](img/dot-net-framework.png)
+ Build the solution.
## Demo Part 1 - tour the Clinic database and the demo application
1. Connect to your database using SSMS without Always Encrypted enabled in the database connection.
- Open SSMS.
- In the **Connect to Server** dialog, enter your server name and authentication parameters. For more information on using SSMS to connect to a Database, [click here](https://azure.microsoft.com/en-us/documentation/articles/sql-database-connect-query-ssms/).
- Click the **Options >>** button and select the **Always Encrypted** tab. Make sure **Enable Always Encrypted** is not selected.
![SSMS](img/ssms-ae-disabled.png)
- Click **Connect**.
1. In SSMS, open and execute **tsql-scripts/ListAllPatients.sql**. The results of this query include sensitive information about patients: social security numbers, names, dates of birth, etc.
1. In SSMS, configure an extended event session, you will use to monitor Transact-SQL queries the demo application sends to the database.
- Open and execute **tsql-scripts/CreateXESession.sql**. This creates an extended event session,
- In Object Explorer, locate the newly created **ClinicDemo** extended event session - under your server, go to **Management/Extended Events/Sessions**.
- Righ-click the **ClinicDemo** session and select **Watch Live Data**. This will open the **ClinicDemo Live Data** window.
![Contoso Clinic Application](img/xe-session-watch-live-data.png)
1. Run the demo application
+ In Visual Studio, start the app (**F5**). This will open the Contoso Clinic application in the default browser.
+ Select the **Patients** tab in the application.
+ Enter a part of a patient's name and the maximum patient age. You should see the patients that meet your search criteria.
![Contoso Clinic Application](img/list-of-patients.png)
1. In SSMS, inspect the queries the demo application sends to the database.
+ Select the **ClinicDemo Live Data** window, which should contain a table with some events your demo application triggered.
+ If the table does not contain the **statement** column, right click on the header of the table and select **Choose Columns...**. Move **statement** to **Selected columns** and click **OK**.
![XEventSessionColumns](img/xevent-session-columns.png)
+ Back in the **ClinicDemo Live Data** window, double click on the statement column in the last row of the table to see the last query the application sent to the database. Inspect the query statement. Note the **WHERE** clause of the query contains the **LIKE** predicate on some string columns and a comparison (**>=**) on the **BirthDate** column.
![XEventSessionColumns](img/xevent-before-encryption.png)
+ Click **OK** to close the statement window.
## Demo Part 2 - Encrypt columns in place
1. Connect to your database using SSMS with Always Encrypted enabled in the database connection.
- Open a new instance of SSMS. (Keep the instance from the previous part of the demo open.)
- In the **Connect to Server** dialog, enter your server name and authentication parameters.
- Click the **Options >>** button and select the **Always Encrypted** tab. Make sure **Enable Always Encrypted** is selected. Enter the URL of your enclave attestation service, you have configured when setting up your demo environment.
![SSMSAEEnabled](img/ssms-ae-enabled.png)
- Click **Connect**.
1. Provision a column master key in SSMS.
+ In Object Explorer, expand your database and navigate to **Security/ Always Encrypted Keys/Column Master Keys**.
+ Right-click on the **Column Master Keys** folder and select **New Column Master Key…**.
![NewCMK](img/new-cmk.png)
+ Enter a column master key name: **CMK1**.
+ Select **Windows Certificate Store - Current User**.
+ Make sure **Allow enclave computations** is selected.
![NewCMK](img/new-cmk-dialog.png)
+ Click **Generate Certificate** to create a new certificate to be used as a column master key.
+ Click **OK**.
1. Provision a column encryption key.
+ In Object Explorer, expand your database and navigate to **Security/ Always Encrypted Keys/Column Encryption Keys**.
+ Right-click on the **Column Encryption Keys** folder and select **New Column Encryption Key…**.
+ Enter a column encryption key name: **CEK1**.
+ Select **CMK1** as the column master key to protect your new column encryption key.
+ Click **OK**.
1. Encrypt a few columns in-place using Transact-SQL.
+ In SSMS, open and review **tsql-scripts/EncryptColumns.sql**. Note the **ALTER TABLE ALTER COLUMN** statements that encrypt three columns: **SSN**, **LastName**, and **BirthDate**. The statements also change the sort order of both string columns to **BIN2**, which is required to support rich queries on those columns.
+ Execute the script.
1. Check if the columns are encrypted.
+ Switch to the other instance of SSMS (from the previous part of the demo) that uses a database connection with Always Encrypted disabled.
+ Rerun the query from **tsql-scripts/ListAllPatients.sql**. Note that the data the **SSN**, **LastName**, **BirthDate** columns are now encrypted.
![NewCMK](img/encrypted-results.png)
1. Test the Contoso Clinic web application.
+ In your web browser, refresh the **Patients** page in the Contoso Clinic application. Notice the application shows plaintext data. This is because Always Encrypted has been already configured in the database connection for the application in the **web.config** file.
+ Enter a part of a patient's name and the maximum patient age. You should see the patients that meet your search criteria.
+ In SSMS, click on the **ClinicDemo Live Data** windows/tab, capturing extending events.
+ Double click on the statement column in the last row of the table to see the last query the application sent to the database. Note the query statement has not changed, but SQL Server now receives encrypted query parameters.
![XEventSessionColumnsAfterEncryption](img/xevent-after-encryption.png)
+ Click **OK** to close the statement window.
## Demo Part 3 - run rich queries from SSMS
1. Enable Parameterization for Always Encrypted the SSMS instance that uses a database connection with Always Encrypted enabled.
+ In the main menu, select **Tools** and then select **Options**
+ In the left pane of the **Options** window, navigate to **Query Execution/SQL Server/Advanced**. Scroll down in the right pane and make sure **Enabled Parameterization for Always Encrypted** is enabled.
![NewCMK](img/parameterization.png)
+ Click **OK**.
1. Open, review (change, if you want) and execute the query in **tsql-scripts/QueryColumns.sql**. The query should return the rows in the **Patients** table meeting the specified search criteria.
## Demo Part 4 - Re-encrypt and decrypt columns in-place
1. Re-encrypt your columns to rotate/change the column encryption key.
+ In the SSMS instance that uses a database connection with Always Encrypted enabled, use Object Explorer to expand your database and navigate to **Security/ Always Encrypted Keys/Column Encryption Keys**.
+ Right-click on the **Column Encryption Keys** folder and select **New Column Encryption Key…**.
+ Enter a column encryption key name: **CEK2**.
+ Select **CMK1** as the column master key to protect your new column encryption key.
+ Click **OK** to provision a new column encryption key.
+ Open and review **tsql-scripts/RotateKeys.sql**. Note that the only difference between this script and **tsql-scripts/EncryptColumns.sql** is the column encryption key specified for the three columns (this script uses **CEK2**). When you execute the script, the columns are re-encrypted using the new column encryption key.
+ Refresh the demo app in the browser. The app should continue to work.
1. Decrypt your columns (convert them back to plaintext).
+ Open, review and execute **tsql-scripts/DecryptColumns.sql**. This script decrypts the encrypted columns. It also re-sets the original collation of the columns.
## Resetting your demo environment
1. Open and execute **tsql-scripts/DecryptColumns.sql**.
1. Open and execute **tsql-scripts/DropKeys.sql**.
1. Close both SSMS windows.
- [Demos of Always Encrypted with secure enclaves in Azure SQL Database](./azure-sql-database/README.md)
- [Demos of Always Encrypted with secure enclaves in SQL Server 2019 using Host Guardian Service for attestation](./server-with-hgs/README.md)
@@ -0,0 +1,344 @@
:::image type="content" source="../../../../manage/sql-server-extended-security-updates/media/solutions-microsoft-logo-small.png" alt-text="solutions-microsoft-logo-small":::
# Always Encrypted with secure enclaves in Azure SQL Database - Demos
The demos in this folder showcase [Always Encrypted with secure enclaves](https://docs.microsoft.com/azure/azure-sql/database/always-encrypted-with-secure-enclaves-landing) in Azure SQL Database. The demos use the Contoso HR web application.
### Contents
[About this sample](#about-this-sample)<br/>
[Before you begin](#before-you-begin)<br/>
[Setup](#setup)<br/>
[Demo 1](#demo-1) - a tour of the demo environment<br/>
[Demo 2](#demo-2) - a short demo of key benefits of secure enclaves<br/>
[Demo 3](#demo-3) - a longer demo showcasing in-place encryption and rich queries<br/>
[Cleanup](#cleanup)<br/>
## About this sample
- **Applies to:** Azure SQL Database
- **Key features:** Always Encrypted with secure enclaves
- **Workload:** Human resources (HR) application
- **Programming Language:** C#, Transact-SQL
- **Authors:** Jakub Szymaszek, Pieter Vanhove
- **Update history:**
## Before you begin
Before you begin, you need an Azure subscription. If you don't already have an Azure subscription, you can get one for free [here](https://azure.microsoft.com/free/).
You also need to make sure the following software is installed on your machine:
1. PowerShell modules:
1. Az version 5.6 or later. For details on how to install the Az PowerShell module, see [Install the Azure Az PowerShell module](https://docs.microsoft.com/powershell/azure/install-az-ps). To determine the version of the Az module installed on your machine, run the following command from a PowerShell session.
```powershell
Get-InstalledModule -Name Az
```
2. Az.Attestation 0.1.8 or later. For details on how to install the Az.Attestation PowerShell module, see [Install Az.Attestation PowerShell module](https://docs.microsoft.com/azure/attestation/quickstart-powershell#install-azattestation-powershell-module). To determine the version of the Az.Attestation module installed on your machine, run the following command from a PowerShell session.
```powershell
Get-InstalledModule -Name Az.Attestation
```
3. SqlServer version 21.1.18245 or later. For details on how to install the SqlServer PowerShell module, see [Installing or updating the SqlServer module](https://docs.microsoft.com/sql/powershell/download-sql-server-ps-module#installing-or-updating-the-sqlserver-module). To determine the version the SqlServer module installed on your machine, run the following command from a PowerShell session.
```powershell
Get-InstalledModule -Name SqlServer
```
1. [Bicep](https://docs.microsoft.com/azure/azure-resource-manager/templates/bicep-overview) version 0.4.63 or later. You need to install Bicep and ensure it can be invoked from PowerShell. The recommended way to achieve that is to [install Bicep manually with PowerShell](https://docs.microsoft.com/azure/azure-resource-manager/templates/bicep-install?tabs=azure-powershell#manual-with-powershell).
1. [SQL Server Management Studio](https://msdn.microsoft.com/en-us/library/mt238290.aspx) - version 18.9.1 or later is recommended.
## Setup
By following the below setup steps, you will create a new resource group and deploy the following resources to your Azure subscription:
- A logical database server.
- The **ContosoHR** database using the [DC-series hardware generation](https://docs.microsoft.com/azure/azure-sql/database/service-tiers-sql-database-vcore#dc-series), which is required for Always Encrypted with secure enclaves.
- A key vault in [Azure Key Vault](https://docs.microsoft.com/azure/key-vault/) and a key to be used as a column master key for Always Encrypted.
- The Contoso HR web application in Azure Web Apps.
- An attestation provider in [Microsoft Azure Attestation](https://docs.microsoft.com/azure/attestation). Your web app will use the attestation provider to attest the enclave in the database.
Setup steps:
1. Clone/download and unpack the repository.
1. Open a PowerShell session.
1. In the PowerShell session, change the directory to the setup folder within this demo's directory. For example, if you've unpacked the downloaded repository on a Windows machine in **C:\\**, issue the following command:
```powershell
cd "C:\sql-server-samples\samples\features\security\always-encrypted-with-secure-enclaves\azure-sql-database\setup"
```
1. Run the **setup.ps1** PowerShell script.
1. When prompted, enter the following information:
1. Your Azure subscription id. To determine your subscription id, see [Find your Azure subscription](https://docs.microsoft.com/azure/media-services/latest/setup-azure-subscription-how-to?tabs=portal).
1. The project name. The resource group containing all your demo resources will have that name. The project name will also be used as a prefix for the names of all demo resources. Please use only lowercase letters and numbers for the project name and make sure it is unique.
1. The location - it must be one of the Azure regions supporting the DC-series hardware generation, which are listed [here](https://docs.microsoft.com/azure/azure-sql/database/service-tiers-sql-database-vcore#dc-series-1).
1. The username and the password of the Azure SQL database server administrator. The setup script will create the server with these admin credentials and it will later use them to connect to the server using SQL authentication for some of the setup steps.
1. When prompted, sign in to Azure. Once you sign in, the script will deploy the demo environment using the provided Bicep template, which may take a few minutes. After the deployment completes, the script performs post-deployment setup steps to configure the database and the attestation policy for Always Encrypted with secure enclaves.
1. When prompted, sign in to Azure again, to enable the SqlServer PowerShell module to connect to the database.
1. Finally, the script outputs the important information about your demo environment.
- Database server name (`<project name>server.database.windows.net`)
- Database name (`ContosoHR`)
- Attestation URL (`https://<project name>attest.<region moniker>.attest.azure.net`)
- Application URL (`https://<project name>app.azurewebsites.net/`)
Please copy and save the above information. You will need it for the demo steps.
## Demo 1
In this demo, you will take a tour of the demo environment, in which Always Encrypted with secure enclaves is already set up and sensitive data columns in the database are already encrypted.
### Prepare for the demo
Perform the below steps before each demo presentation.
1. Close all running instances of SQL Server Management Studio (SSMS).
1. Prepare a new instance of SSMS.
1. Start SSMS.
1. In the Connect to Server dialog:
1. In the main page of the dialog, enter your database server name. Set **Authentication** to **Azure Active Directory Universal with MFA**. In the **User Name** field, enter your Azure AD username. You should enter the same username, you used to sign in to Azure, when you set up your demo environment.
![Connect to Server](./img/ssms-connect-to-server-main-page.png)
1. Click **Connect**.
1. When prompted, sign in to Azure.
1. Prepare your web browser.
1. Open a new tab in the browser and point it to Azure Portal: **https://portal.azure.com**.
1. Sign in to Azure if prompted.
1. In the **Search** box in the Azure Portal, enter the name of your demo resource group and click **Enter**. In the search results, click on your resource group. You should see the content of your resource group, which should look like this:
![Demo resource group](./img/resource-group.png)
### Demos steps
1. Review the content of your demo resource group. It should contain the following resources:
- `<project name>app`- an app service hosting the Contoso HR web application.
- `<project name>attest`- an attestation provider in Microsoft Azure Attestation for attesting the secure enclave for the **ContosoHR** database.
- `<project name>identity` - a user-assigned managed identity that was used to deploy the web application.
- `<project name>plan` - an app service plan for the web application.
- `<project name>server`- a logical server in Azure SQL Database.
- `<project name>vault` - a key vault in Azure Key Vault, containing the column master key for Always Encrypted.
- `ContosoHR` - a database.
1. Right-click on the **ContosoHR** database in the resource group and open its **Overview** blade in the new tab. Click on **Compute + storage** under **Settings**. Click **Change configuration**. Note that the database is already configured to use the DC-series hardware configuration that supports confidential computing using secure enclaves. Setting the DC-series hardware configuration for a database is required to use Always Encrypted with secure enclaves in the database. For more information, see [Enable Intel SGX for your Azure SQL Database](https://docs.microsoft.com/azure/azure-sql/database/always-encrypted-enclaves-enable-sgx).
![DC-series hardware configuration](./img/portal-dc-series-configuration.png)
1. Close the browser tab for the database. Right-click on the attestation provider in your resource group and open its **Overview** blade in a new tab. Click on **Policy** under **Settings**. Select **SGX-IntelSDK** for **Attestation Type**. This will display the attestation policy configured for Intel Software Guard eXtensions (Intel SGX) enclaves. The policy allows a client driver within an application to verify the secure enclave in Azure SQL Database is a genuine Intel SGX enclave and it runs the genuine SQL library that implements Transact-SQL predicates and cryptographic operations of Always Encrypted. For more information, see [Configure Azure Attestation for your Azure SQL logical server](https://docs.microsoft.com/azure/azure-sql/database/always-encrypted-enclaves-configure-attestation).
![DC-series hardware configuration](./img/portal-attestation-policy.png)
1. Close the browser tab for the attestation provider. Right-click on the app service for the Contoso HR web application in your resource group and open its **Overview** blade in a new tab. Click on **Configuration** under **Settings**. In the **Connection strings** section, click **Advanced edit**. This will display the database connection string configured for the web application. There are three important things to call out in the database connection string:
- **Column Encryption Setting = Enabled** turns the Always Encrypted on in the client driver, allowing it to transparently encrypt query parameters and decrypt query results.
- **Attestation Protocol = AAS** specifies Microsoft Azure Attestation is used for attesting the secure enclave for the **ContosoHR** database.
- **Enclave Attestation Url** is an attest URI of the attestation provider.
![Connection string](./img/portal-web-app-connection-string.png)
1. Close the browser tab for the app service. Right-click on the key vault in your resource group and open its **Overview** blade in a new tab.
1. Click on **Keys** under **Settings**. You should see the entry for the key, named **CMK** - this is your column master key for Always Encrypted.
![Connection string](./img/portal-key-vault-key.png)
2. Click on **Access Policies** under **Settings**. You should see two access policy entries: one for your identity and one for the web app's identity. These policies grant you permissions necessary to perform key management operations and they grant the web app permissions required to decrypt column encryption keys, protecting the data.
1. Switch to SSMS.
1. In Object Explorer, navigate to the **ContosoHR** database. Then go to **Security** > **Always Encrypted Keys**.
1. Open the **Column Master Keys** and **Column Encryption Keys** folders. You should see the metadata object, named **CMK1**, for the column master key and the metadata object, named **CEK1**, for the column encryption key.
1. Right click on **CMK1** and select **Properties**. Note that the metadata object references the key in the key vault. Also note **Enclave Computations** is set to **Allowed**, which permits the column encryption key, this columns master key protects, to be used in enclave computations.
![Connection string](./img/ssms-cmk.png)
### Key Takeaways
Always Encrypted with secure enclaves requires specific hardware that is exposed in Azure SQL Database as the DC-series hardware configuration. Microsoft Azure Attestation is a Platform-as-a-Service solution for attestation enclaves in Azure. Enclaves are attested against a policy, you define and control.
## Demo 2
This short demo highlights the main benefits of Always Encrypted with secure enclaves. The starting point for the demo is the ContosoHR database with the **SSN** and **Salary** columns already encrypted.
### Prepare for the demo
Perform the below steps before you show the demo.
1. Close all running instances of SQL Server Management Studio (SSMS).
1. Prepare a new instance of SSMS.
1. Start SSMS.
1. In the **Connect to Server** dialog:
1. In the main page of the dialog, enter your database server name. Set **Authentication** to **Azure Active Directory Universal with MFA**. In the **User Name** field, enter your Azure AD username. You should enter the same username, you used to sign in to Azure, when you set up your demo environment.
![Connect to Server](./img/ssms-connect-to-server-main-page.png)
1. Click the **Options >>** button, select the **Connection Properties** tab and enter the database name (**ContosoHR**).
![Connection Properties](./img/ssms-connect-to-server-connection-properties-page.png)
1. Select the **Always Encrypted** tab. Make sure the **Enable Always Encrypted** checkbox is **not** selected.
![Always Encrypted disabled](./img/ssms-connect-to-server-always-encrypted-disabled.png)
1. Click **Connect**.
1. When prompted, sign in to Azure.
1. Configure query windows.
1. In Object Explorer, find and select the **ContosoHR** database.
![Selecting database](./img/ssms-explorer-select-database.png)
1. With the **ContosoHR** database selected, click Ctrl + O. In the **Open File** dialog, navigate to the **tsql-scripts** folder and select **ListAllEmployees.sql**. Do not execute the query yet.
1. With the **ContosoHR** database selected, click Ctrl + O. In the **Open File** dialog, navigate to the **tsql-scripts** folder and select **QueryEvents.sql**. Do not execute the query yet.
1. Prepare your web browser.
1. Open your browser.
1. Point the browser to the demo application URL.
![Web app](./img/web-app.png)
### Demos steps
1. Show the Contoso HR web app in the the browser. This application displays employee records and it allows you to filter and sort employees by salary or by a portion of the social security number (SSN). Move the salary slider and enter a couple of digits in the search box to filter by salary and SSN. Click on the **Ssn** or **Salary** column header to filter by salary or SSN.
![Web app filtering](./img/web-app-filtering.png)
1. Switch to SSMS, select the **ListAllEmployees.sql** tab and click **F5** to execute the query, which shows the content of the **Employees** table, the web application uses as a data store. Although you are a DBA of the database, you cannot see the plaintext data in the **SSN** and **Salary** columns, as those two columns are protected with Always Encrypted.
![Encrypted results](./img/ssms-encrypted-results.png)
1. Select the **QueryXevents.sql** tab and click **F5** to execute the query. This query retrieves extended events from the **Demo** extended event session, configured in the **ContosoHR** database. Each extended event captures a query the web application has sent to the database.
![Extended event results](./img/ssms-xevents-results.png)
1. Click on the link in the second column of the first row of the result set to see the extended event with the latest query from the application. This will open the extended event in the new tab.
1. Review the query statement. Note that the query contains the **WHERE** clause with rich computations on encrypted columns: pattern matching using the **LIKE** predicate on the **SSN** column and the range comparison on the **Salary** column. The query also sorts records (the **ORDER BY** clause) by **SSN** or **Salary**. **Pro Tip:** to make it easier to view the query statement, you can put line brakes in it.
![Extended event](./img/ssms-xevent.png)
1. Locate the value of query parameters: **@SSNSearchPattern**, **@MinSalary**, **@MaxSalary**. Note that the values of the parameters are encrypted the client driver inside the web app transparently encrypts parameters corresponding to encrypted columns, before sending the query to the database. Not only does not the DBA have access to sensitive data in the database, but the DBA cannot see the plaintext values of query parameters used to process that data either.
### Key Takeaways
Always Encrypted with secure enclaves allows applications to perform rich queries on sensitive data without revealing the data to potentially malicious insiders, including DBAs in your organization.
## Demo 3
The starting point for this demo is the database with no columns encrypted - the data is initially not protected. The demo shows how to reach the following two objectives:
1. Protect sensitive data in the database by encrypting it in-place.
1. Ensure the Contoso HR web application can continue run rich queries on database columns after encrypting the columns.
During the demo, you will use two instances of SQL Server Management Studio (SSMS):
- DBA's instance - when using it, you will assume the role of a DBA.
- Security Adminsitrator's instance - when using it, you will assume the role of a Security Administrator, who configures Always Encrypted in the database.
### Prepare for the demo
Perform the below steps before you show the demo.
1. Close all running SSMS instances.
1. Prepare DBA's instance of SSMS.
1. Start SSMS.
1. In the Connect to Server dialog:
1. In the main page of the dialog, enter your database server name. Set **Authentication** to **Azure Active Directory Universal with MFA**. In the **User Name** field, enter your Azure AD username. You should enter the same username, you've used to sign in to Azure, when you set up your demo environment.
![Connect to Server](./img/ssms-connect-to-server-main-page.png)
1. Click the **Options >>** button, select the **Connection Properties** tab and enter the database name (**ContosoHR**).
![Connection Properties](./img/ssms-connect-to-server-connection-properties-page.png)
1. Select the **Always Encrypted** tab. Make sure the **Enable Always Encrypted** checkbox is **not** selected.
![Always Encrypted disabled](./img/ssms-connect-to-server-always-encrypted-disabled.png)
1. Click **Connect**.
1. When prompted, sign in to Azure.
1. Configure query windows.
1. In Object Explorer, find and select the **ContosoHR** database.
![Selecting database](./img/ssms-explorer-select-database.png)
1. With the **ContosoHR** database selected, click Ctrl + O. In the **Open File** dialog, navigate to the **tsql-scripts** folder and select **ListAllEmployees.sql**. Do not execute the query yet.
1. With the **ContosoHR** database selected, click Ctrl + O. In the **Open File** dialog, navigate to the **tsql-scripts** folder and select **QueryEvents.sql**. Do not execute the query yet.
1. Prepare Security Administrator's instance of SSMS.
1. Start SSMS.
1. In the Connect to Server dialog:
1. In the main page of the dialog, enter your database server name. Set **Authentication** to **Azure Active Directory Universal with MFA**. In the **User Name** field, enter your Azure AD username. You should enter the same username, you've used to sign in to Azure, when you set up your demo environment.
![Connect to Server](./img/ssms-connect-to-server-main-page.png)
1. Click the **Options >>** button, select the **Connection Properties** tab and enter the database name (**ContosoHR**).
![Connection Properties](./img/ssms-connect-to-server-connection-properties-page.png)
1. Select the **Always Encrypted** tab. Make sure the **Enable Always Encrypted** checkbox **is** selected. Enter your attestation URL.
![Always Encrypted disabled](./img/ssms-connect-to-server-always-encrypted-enabled.png)
1. Click **Connect**.
1. When prompted, sign in to Azure.
1. Configure query windows.
1. In Object Explorer, find and select the **ContosoHR** database.
![Selecting database](./img/ssms-explorer-select-database.png)
1. With the **ContosoHR** database selected, click Ctrl + O. In the **Open File** dialog, navigate to the **tsql-scripts** folder and select **DecryptColumns.sql**. **Click **F5** to execute the query**, which will decrypt the **SSN** and **Salary** columns in the database.
1. With the **ContosoHR** database selected, click Ctrl + O. In the **Open File** dialog, navigate to the **tsql-scripts** folder and select **EncryptColumns.sql**. Do not execute the query yet.
1. Prepare your web browser.
1. Open your browser.
1. Point the browser to the demo application URL.
![Web app](./img/web-app.png)
### Demos steps
1. Show the Contoso HR web app in the the browser. This application displays employee records and allows you to filter employees by salary or by a portion of the social security number (SSN). Move the salary slider and enter a couple of digits in the search box to filter by salary and SSN.
![Web app filtering](./img/web-app-filtering.png)
1. Switch to DBA's instance of SSMS, select the **ListAllEmployees.sql** tab and click **F5** to execute the query, which shows the content of the **Employees** table, the web application uses as a data store. As a DBA, you can view all sensitive information about employees, including the data stored in the **SSN** and **Salary** columns. A malicious DBA could easily exfiltrate the data by running a simple query like this one.
![Encrypted results](./img/ssms-plaintext-results.png)
1. Select the **QueryXevents.sql** tab and click **F5** to execute the query. This query retrieves extended events from the **Demo** extended event session, configured in the **ContosoHR** database. Each extended event captures a query the web application has sent to the database.
![Extended event results](./img/ssms-xevents-results.png)
1. Click on the link in the second column of the first row of the result set to see the extended event with the latest query from the application. This will open the extended event in the new tab.
1. Review the query statement. Note that the query contains the **WHERE** clause with rich computations on encrypted columns: pattern matching using the **LIKE** predicate on the **SSN** column and the range comparison on the **Salary** column. The query also sorts records (the **ORDER BY** clause) by **SSN** or **Salary**. **Pro Tip:** to make it easier to view the query statement, you can put line brakes in it.
![Extended event with plaintext parameters](./img/ssms-xevent-plaintext.png)
1. Locate the value of query parameters: **@SSNSearchPattern**, **@MinSalary**, **@MaxSalary**. Note that the values of the parameters are in plaintext, as the columns, the parameters correspond to, are not encrypted.
1. Switch to Security Administrator's instance of SSMS, select the **EncryptColumns.sql** tab and click **F5** to execute the query, encrypts the data in the **SSN** and **Salary** columns in place, using the secure enclave.
1. Switch back to DBA's instance of SSMS, select the **ListAllEmployees.sql** tab and click **F5** to execute the query again. Now the query should show the encrypted data in the **SSN** and **Salary** columns. As both columns are encrypted, the DBA cannot see the data in plaintext.
![Encrypted results](./img/ssms-encrypted-results.png)
1. In the web browser, move the slider to reset the filter for salary and then re-enter a few digits of an SSN. Confirm the application still can filter employee records by salary and SSN.
1. Switch to DBA's instance of SSMS, select the **QueryXevents.sql** tab and click **F5** to re-run the query.
1. Click on the link in the second column of the first row of the result set to see the extended event with the latest query from the application. This will open the extended event in the new tab.
1. Review the query statement. Note that the query statement the query sends to the database has not changed - it still contains pattern matching using the **LIKE** predicate on the **SSN** column and the range comparison on the **Salary** column, as well as sorting (the **ORDER BY** clause) by **SSN** or **Salary**. **Pro Tip:** to make it easier to view the query statement, you can put line brakes in it.
![Extended event](./img/ssms-xevent.png)
1. Locate the value of query parameters: **@SSNSearchPattern**, **@MinSalary**, **@MaxSalary**. Note that the values of the parameters are now encrypted the client driver inside the web app transparently encrypts parameters corresponding to encrypted columns, before sending the query to the database. Not only does not the DBA have access to sensitive data in the database, but the DBA cannot see the plaintext values of query parameters used to process that data either.
### Key Takeaways
Secure enclaves make it possible to encrypt sensitive data columns in-place, eliminating a need to move the data outside of the database for cryptographic operations.
The unique benefit of Always Encrypted with secure enclaves is that it allows you to protect your sensitive data from high-privilege users, including DBAs in your organization, and, after you encrypt your data to protect it, your applications can continue running rich queries on encrypted columns.
## Cleanup
To permanently remove all demo resources:
1. Run the **cleanup.ps1** PowerShell script in the setup folder.
1. When prompted, enter your demo resource group name and your subscripton id, and sign in to Azure.
1. Confirm you want to delete resource group.
1. Confirm you want to permanently delete the key vault.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

@@ -0,0 +1,9 @@
version= 1.0;
authorizationrules
{
[ type=="x-ms-sgx-is-debuggable", value==false ]
&& [ type=="x-ms-sgx-product-id", value==4639 ]
&& [ type=="x-ms-sgx-svn", value>= 0 ]
&& [ type=="x-ms-sgx-mrsigner", value=="e31c9e505f37a58de09335075fc8591254313eb20bb1a27e5443cc450b6e33e5"]
=> permit();
};
@@ -0,0 +1,30 @@
IF EXISTS (SELECT *
FROM sys.database_event_sessions
WHERE name = 'Demo')
BEGIN
DROP EVENT SESSION Demo
ON Database;
END
go
CREATE EVENT SESSION [Demo] ON DATABASE
ADD EVENT sqlserver.rpc_completed(SET collect_data_stream=(1),collect_statement=(1)
ACTION(sqlserver.sql_text)
WHERE ([sqlserver].[like_i_sql_unicode_string]([sqlserver].[sql_text],N'%SSN%')AND [package0].[not_equal_unicode_string]([statement],N'exec sp_reset_connection'))
)
ADD TARGET package0.ring_buffer
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO
--CREATE EVENT SESSION [Demo] ON DATABASE
--ADD EVENT sqlserver.rpc_completed(SET collect_data_stream=(1),collect_statement=(1)
-- WHERE ([sqlserver].[equal_i_sql_unicode_string]([sqlserver].[database_name],N'ContosoHR') AND [package0].[not_equal_unicode_string]([statement],N'exec sp_reset_connection')))
--ADD TARGET package0.ring_buffer(SET max_memory=(4096))
--WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=2 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=ON)
--GO
ALTER EVENT SESSION [Demo]
ON DATABASE
STATE = START; -- STOP;
@@ -0,0 +1,290 @@
/****** Object: Table [dbo].[Employees] Script Date: 4/7/2021 9:31:31 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Employees](
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[SSN] [char](11) NOT NULL,
[FirstName] [nvarchar](50) NOT NULL,
[LastName] [nvarchar](50) NOT NULL,
[Salary] [money] NOT NULL,
CONSTRAINT [PK_dbo.Employees] PRIMARY KEY CLUSTERED
(
[EmployeeID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('795-73-9838', 'Catherine', 'Abel', 31692)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('990-00-6818', 'Kim', 'Abercrombie', 990)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('009-37-3952', 'Frances', 'Adams', 5684)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('708-44-3627', 'Jay', 'Adams', 55415)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('447-62-6279', 'Robert', 'Ahlering', 49744)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('872-78-4732', 'Stanley', 'Alan', 38584)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('898-79-8701', 'Paul', 'Alcorn', 11918)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('561-88-3757', 'Mary', 'Alexander', 17349)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('904-55-0991', 'Michelle', 'Alexander', 70796)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('293-95-6617', 'Marvin', 'Allen', 96956)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('260-99-4784', 'Oscar', 'Alpuerto', 18386)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('605-29-1370', 'Ramona', 'Antrim', 72548)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('731-35-9387', 'Thomas', 'Armstrong', 72180)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('854-76-1401', 'John', 'Arthur', 79054)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('775-20-2697', 'Chris', 'Ashton', 6011)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('117-79-5230', 'Teresa', 'Atkinson', 87089)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('607-41-3750', 'Stephen', 'Ayers', 72344)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('412-66-3694', 'James', 'Bailey', 33950)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('775-63-2547', 'Douglas', 'Baldwin', 89945)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('779-52-1722', 'Wayne', 'Banack', 73236)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('647-03-0271', 'Robert', 'Barker', 23861)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('353-98-6954', 'John', 'Beaver', 75812)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('817-89-8819', 'John', 'Beaver', 19047)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('611-82-6762', 'Edna', 'Benson', 59478)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('665-55-5653', 'Payton', 'Benson', 98459)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('947-37-8651', 'Robert', 'Bernacchi', 78183)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('071-31-7824', 'Robert', 'Bernacchi', 79399)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('610-55-3726', 'Matthias', 'Berndt', 20209)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('590-27-0856', 'Jimmy', 'Bischoff', 24730)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('008-73-9012', 'Mae', 'Black', 60057)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('137-23-1723', 'Donald', 'Blanton', 65301)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('969-53-6095', 'Michael', 'Blythe', 5635)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('222-42-8458', 'Gabriel', 'Bockenkamp', 41020)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('163-08-2988', 'Luis', 'Bonifaz', 85014)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('898-11-0280', 'Cory', 'Booth', 49351)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('432-52-2738', 'Randall', 'Boseman', 24063)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('018-29-9539', 'Cornelius', 'Brandon', 91513)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('472-36-9060', 'Richard', 'Bready', 26084)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('566-87-9214', 'Ted', 'Bremer', 95506)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('771-34-9714', 'Alan', 'Brewer', 35682)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('413-73-7072', 'Walter', 'Brian', 5198)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('497-65-6363', 'Christopher', 'Bright', 24216)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('450-26-2195', 'Willie', 'Brooks', 55078)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('052-78-7929', 'Jo', 'Brown', 17396)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('087-92-6356', 'Robert', 'Brown', 99872)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('048-71-8953', 'Steven', 'Brown', 88633)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('488-68-6075', 'Mary', 'Browning', 59229)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('819-63-3780', 'Michael', 'Brundage', 30996)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('599-61-7739', 'Shirley', 'Bruner', 35872)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('772-36-0661', 'June', 'Brunner', 84642)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('990-78-3760', 'Megan', 'Burke', 76800)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('103-38-5903', 'Karren', 'Burkhardt', 93306)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('146-43-8832', 'Linda', 'Burnett', 56479)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('448-72-5948', 'Jared', 'Bustamante', 7997)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('254-13-4819', 'Barbara', 'Calone', 28720)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('666-45-9926', 'Lindsey', 'Camacho', 11870)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('260-46-2402', 'Frank', 'Campbell', 87501)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('189-92-4702', 'Henry', 'Campen', 30494)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('718-12-8401', 'Chris', 'Cannon', 2324)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('918-66-9747', 'Jane', 'Carmichael', 22620)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('806-97-1958', 'Jovita', 'Carmody', 37601)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('482-61-8230', 'Rob', 'Caron', 16904)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('897-39-6229', 'Andy', 'Carothers', 40261)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('656-79-6279', 'Donna', 'Carreras', 78393)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('745-99-0161', 'Rosmarie', 'Carroll', 98109)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('216-16-4120', 'Raul', 'Casts', 41706)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('102-56-5530', 'Matthew', 'Cavallari', 75290)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('787-23-4125', 'Andrew', 'Cencini', 75121)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('424-55-1778', 'Stacey', 'Cereghino', 25456)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('572-19-0999', 'Forrest', 'Chandler', 49996)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('745-81-6513', 'Lee', 'Chapla', 70991)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('947-66-5585', 'Yao-Qiang', 'Cheng', 72455)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('531-83-4784', 'Nicky', 'Chesnut', 13676)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('109-99-0299', 'Ruth', 'Choin', 55818)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('483-85-6853', 'Anthony', 'Chor', 71421)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('567-39-1024', 'Pei', 'Chow', 57284)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('382-49-7387', 'Jill', 'Christie', 94767)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('336-03-8102', 'Alice', 'Clark', 47963)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('994-22-9926', 'Connie', 'Coffman', 32136)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('129-28-7723', 'John', 'Colon', 82858)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('347-44-2949', 'Scott', 'Colvin', 1121)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('706-66-0382', 'Scott', 'Cooper', 22281)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('500-63-2220', 'Eva', 'Corets', 34410)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('942-15-7859', 'Marlin', 'Coriell', 43154)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('143-78-9971', 'Jack', 'Creasey', 66527)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('372-18-7905', 'Grant', 'Culbertson', 69903)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('153-33-1155', 'Scott', 'Culp', 87717)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('790-69-5423', 'Megan', 'Davis', 69707)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('105-16-8373', 'Alvaro', 'De Matos Miranda Filho', 15198)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('989-18-3523', 'Aidan', 'Delaney', 86115)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('771-07-7325', 'Stefan', 'Delmarco', 50994)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('545-83-9747', 'Prashanth', 'Desai', 97968)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('994-72-1605', 'Bev', 'Desalvo', 76954)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('558-23-5595', 'Brenda', 'Diaz', 4027)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('752-63-1338', 'Blaine', 'Dockter', 12312)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('825-10-8923', 'Cindy', 'Dodd', 82876)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('368-69-8964', 'Patricia', 'Doyle', 97282)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('597-44-1424', 'Gerald', 'Drury', 20153)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('957-28-1545', 'Bart', 'Duncan', 89903)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('187-17-8616', 'Maciej', 'Dusza', 6725)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('194-76-9481', 'Carol', 'Elliott', 49287)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('679-79-8165', 'Shannon', 'Elliott', 94194)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('367-73-8845', 'John', 'Emory', 65560)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('814-03-3691', 'Gail', 'Erickson', 99845)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('214-28-9968', 'Mark', 'Erickson', 95242)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('913-55-6645', 'Ann', 'Evans', 48046)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('763-33-5650', 'John', 'Evans', 59254)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('658-41-8532', 'Twanna', 'Evans', 35364)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('943-41-6011', 'Carolyn', 'Farino', 76201)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('691-30-8623', 'Geri', 'Farrell', 39600)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('361-08-3217', 'François', 'Ferrier', 3704)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('827-51-1487', 'Kathie', 'Flood', 78467)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('483-93-0057', 'John', 'Ford', 77552)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('738-37-1607', 'Garth', 'Fort', 34381)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('822-59-9384', 'Dorothy', 'Fox', 32679)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('309-79-2521', 'Mihail', 'Frintu', 52898)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('814-32-0421', 'Paul', 'Fulton', 1320)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('076-35-8143', 'Michael', 'Galos', 24061)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('620-03-4764', 'Jon', 'Ganio', 70002)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('769-76-3600', 'Dominic', 'Gash', 89479)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('160-92-3129', 'Janet', 'Gates', 49178)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('318-00-6667', 'Janet', 'Gates', 68327)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('982-77-3975', 'Orlando', 'Gee', 53281)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('328-68-1544', 'Darren', 'Gehring', 13353)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('053-51-9173', 'Jim', 'Geist', 91180)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('840-05-6646', 'Guy', 'Gilbert', 3780)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('446-11-2924', 'Janet', 'Gilliat', 89588)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('273-16-2522', 'Mary', 'Gimmi', 53352)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('003-23-9305', 'Jeanie', 'Glenn', 49086)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('878-44-1968', 'Scott', 'Gode', 2744)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('910-05-4138', 'Mete', 'Goktepe', 89053)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('912-33-3174', 'Abigail', 'Gonzalez', 56550)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('058-26-3234', 'Michael', 'Graff', 41144)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('687-28-3396', 'Douglas', 'Groncki', 22262)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('496-75-4904', 'Brian', 'Groth', 35712)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('071-00-8057', 'Erin', 'Hagens', 48197)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('288-05-9705', 'Betty', 'Haines', 6286)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('002-47-6040', 'Jean', 'Handley', 22940)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('936-84-1664', 'Kerim', 'Hanif', 80093)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('216-03-0835', 'John', 'Hanson', 10237)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('525-81-0810', 'Lucy', 'Harrington', 69388)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('331-25-9319', 'Keith', 'Harris', 7729)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('770-01-5105', 'Keith', 'Harris', 67336)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('208-84-9956', 'Roger', 'Harui', 78271)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('633-34-5095', 'Ann', 'Hass', 62689)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('022-89-0200', 'Valerie', 'Hendricks', 10269)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('912-85-8027', 'Cheryl', 'Herring', 95103)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('307-29-4403', 'Ronald', 'Heymsfield', 30421)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('083-68-5072', 'Mike', 'Hines', 35109)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('239-20-6174', 'Matthew', 'Hink', 75140)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('162-43-8489', 'Bob', 'Hodges', 84875)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('717-37-3032', 'David', 'Hodgson', 15920)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('280-15-5623', 'Helge', 'Hoeing', 23680)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('353-00-9496', 'Juanita', 'Holman', 83632)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('162-65-6542', 'Peter', 'Houston', 6335)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('427-43-7296', 'George', 'Huckaby', 90908)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('603-61-0319', 'Joshua', 'Huff', 55166)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('877-01-3415', 'Phyllis', 'Huntsman', 58528)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('391-25-8382', 'Phyllis', 'Huntsman', 86920)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('626-56-2930', 'Lawrence', 'Hurkett', 11698)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('731-19-3470', 'Lucio', 'Iallo', 83202)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('171-33-3481', 'Richard', 'Irwin', 83990)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('828-77-1376', 'Erik', 'Ismert', 7706)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('611-48-4137', 'Eric', 'Jacobsen', 784)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('477-14-7161', 'Jodan', 'Jacobson', 17695)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('738-83-2602', 'Sean', 'Jacobson', 9412)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('589-30-3617', 'Joyce', 'Jarvis', 42556)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('538-28-5108', 'Barry', 'Johnson', 36190)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('661-09-8547', 'Barry', 'Johnson', 80820)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('025-55-7602', 'Brian', 'Johnson', 54767)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('422-34-6020', 'David', 'Johnson', 26695)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('500-92-8728', 'Tom', 'Johnston', 52805)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('170-89-3691', 'Jean', 'Jordan', 68936)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('108-25-0733', 'Peggy', 'Justice', 39764)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('392-44-2253', 'Sandeep', 'Kaliyath', 84549)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('666-10-8497', 'Sandeep', 'Katyal', 27585)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('738-29-5963', 'John', 'Kelly', 31491)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('253-47-6467', 'Robert', 'Kelly', 43239)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('059-47-6363', 'Kevin', 'Kennedy', 18192)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('382-51-9530', 'Mitch', 'Kennedy', 68959)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('480-79-4419', 'Imtiaz', 'Khan', 66870)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('097-39-6667', 'Karan', 'Khanna', 17048)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('625-86-1609', 'Anton', 'Kirilov', 97398)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('109-78-5455', 'Christian', 'Kleinerman', 22492)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('914-28-0431', 'Andrew', 'Kobylinski', 47853)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('428-15-1588', 'Eugene', 'Kogan', 61377)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('612-70-0567', 'Scott', 'Konersmann', 87060)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('025-76-2628', 'Joy', 'Koski', 165)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('920-75-2262', 'Diane', 'Krane', 12316)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('748-12-0172', 'Kay', 'Krane', 13614)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('230-78-1884', 'Kay', 'Krane', 98499)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('121-03-7961', 'Margaret', 'Krupka', 98845)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('993-16-0574', 'Peter', 'Kurniawan', 67823)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('560-17-8321', 'Jeffrey', 'Kurtz', 18840)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('176-23-2126', 'Eric', 'Lang', 39005)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('678-53-5154', 'Elsa', 'Leavitt', 71752)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('950-17-4726', 'Marjorie', 'Lee', 28116)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('649-28-8360', 'Roger', 'Lengel', 75588)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('507-95-7549', 'A.', 'Leonetti', 71639)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('442-99-4943', 'Bonnie', 'Lepro', 9089)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('674-71-8512', 'Elsie', 'Lewin', 93630)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('418-20-4458', 'George', 'Li', 74540)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('986-20-3872', 'Joseph', 'Lique', 96968)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('716-41-6291', 'Paulo', 'Lisboa', 11243)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('475-64-2482', 'Paulo', 'Lisboa', 84392)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('471-03-3608', 'David', 'Liu', 57309)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('440-49-4765', 'Jinghao', 'Liu', 34991)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('321-33-7277', 'Kevin', 'Liu', 20305)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('113-69-0506', 'Sharon', 'Looney', 5368)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('311-21-1551', 'Judy', 'Lundahl', 15667)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('710-73-5330', 'Denise', 'Maccietto', 2258)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('321-14-1319', 'Scott', 'MacDonald', 30158)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('340-75-5874', 'Kathy', 'Marcovecchio', 10228)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('043-85-0648', 'Melissa', 'Marple', 46100)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('253-33-7509', 'Frank', 'Mart¡nez', 46267)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('335-68-4629', 'Chris', 'Maxwell', 92921)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('535-30-6577', 'Sandra', 'Maynard', 60264)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('613-34-9127', 'Walter', 'Mays', 70620)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('754-70-9484', 'Lola', 'McCarthy', 75175)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('758-46-9282', 'Jane', 'McCarty', 78021)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('287-44-2853', 'Yvonne', 'McKay', 38787)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('173-12-0893', 'Nkenge', 'McLin', 27222)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('028-18-1290', 'R. Morgan', 'Mendoza', 80464)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('035-47-1686', 'Helen', 'Meyer', 79306)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('926-47-4349', 'Dylan', 'Miller', 64819)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('947-84-3762', 'Frank', 'Miller', 97091)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('598-00-4792', 'Virginia', 'Miller', 77286)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('069-59-6908', 'Virginia', 'Miller', 57881)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('864-49-8796', 'Neva', 'Mitchell', 36848)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('484-78-0561', 'Joseph', 'Mitzner', 42987)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('684-27-6433', 'Margaret', 'Smith', 46020)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('521-85-5433', 'Laura', 'Steele', 87969)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('239-08-6212', 'Alan', 'Steiner', 71635)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('655-64-2836', 'Alice', 'Steiner', 60544)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('850-56-7206', 'Derik', 'Stenerson', 16798)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('077-42-9130', 'Vassar', 'Stern', 55399)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('094-90-4314', 'Wathalee', 'Steuber', 20296)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('409-83-6433', 'Liza Marie', 'Stevens', 6631)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('351-34-7304', 'Robert', 'Stotka', 88774)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('866-96-8557', 'Kayla', 'Stotler', 19835)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('716-37-0786', 'Ruth', 'Suffin', 38058)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('689-86-1583', 'Elizabeth', 'Sullivan', 98566)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('622-25-6018', 'Michael', 'Sullivan', 9242)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('137-21-0253', 'Brad', 'Sutton', 44883)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('237-99-8262', 'Abraham', 'Swearengin', 85958)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('518-41-6271', 'Julie', 'Taft-Rider', 63622)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('554-31-5202', 'Clarence', 'Tatman', 25188)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('591-44-7136', 'Chad', 'Tedford', 22306)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('069-09-3831', 'Vanessa', 'Tench', 44398)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('523-93-8801', 'Judy', 'Thames', 89067)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('266-03-4963', 'Daniel', 'Thompson', 36910)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('496-54-0726', 'Donald', 'Thompson', 85206)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('984-43-6756', 'Kendra', 'Thompson', 7375)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('047-31-4129', 'Diane', 'Tibbott', 95073)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('217-20-9777', 'Delia', 'Toone', 17369)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('420-28-0429', 'Michael John', 'Troyer', 69381)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('236-75-2355', 'Christie', 'Trujillo', 79299)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('984-20-5166', 'Sairaj', 'Uddin', 41036)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('353-75-0098', 'Sunil', 'Uppal', 23245)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('934-96-8406', 'Jessie', 'Valerio', 62357)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('773-23-3159', 'Gregory', 'Vanderbout', 21434)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('951-08-3331', 'Michael', 'Vanderhyde', 72246)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('856-23-4990', 'Margaret', 'Vanderkamp', 18918)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('359-67-5826', 'Gary', 'Vargas', 37729)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('743-66-8203', 'Nieves', 'Vargas', 61754)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('368-97-5673', 'Ranjit', 'Varkey Chudukatil', 19423)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('890-04-1424', 'Patricia', 'Vasquez', 59489)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('267-37-1036', 'Wanda', 'Vernon', 11749)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('101-14-5907', 'Robert', 'Vessa', 58459)
INSERT INTO [dbo].[Employees] ([SSN], [FirstName], [LastName], [Salary]) VALUES ('148-51-2717', 'Caroline', 'Vicknair', 83361)
@@ -0,0 +1,231 @@
///////////////////////
// Define parameters //
///////////////////////
@description('The project name. The names of all resources will be derived from the project name.')
param projectName string
@description('The object id of the user running the deployment.')
param userObjectId string
@description('The username of the user running the deployment.')
param userName string
@description('The username of the Azure SQL database server administrator for SQL authentication.')
param sqlAdminUserName string
@description('The password of the Azure SQL database server administrator for SQL authentication.')
param sqlAdminPassword string
@description('The IP address the user will connect from to the logical server in Azure SQL Database.')
param clientIP string
@description('The location (the Azure region) for all resources.')
param location string = resourceGroup().location
////////////////////////////////////////////
// Create and configure a logical server //
////////////////////////////////////////////
// Create the server
var SQLServerName_var = '${projectName}server'
resource Server_Name_resource 'Microsoft.Sql/servers@2019-06-01-preview' = {
name: SQLServerName_var
location: location
tags: {}
identity: {
type: 'SystemAssigned'
}
properties: {
administratorLogin: sqlAdminUserName
administratorLoginPassword: sqlAdminPassword
//version: 'string' //optional
minimalTlsVersion: '1.2'
publicNetworkAccess: 'Enabled'
}
}
// Allow Azure services and resources to access this server
resource Server_Name_AllowAllWindowsAzureIps 'Microsoft.Sql/servers/firewallRules@2015-05-01-preview' = {
name: '${Server_Name_resource.name}/AllowAllWindowsAzureIps'
properties: {
endIpAddress: '0.0.0.0'
startIpAddress: '0.0.0.0'
}
}
// Allow Client IP to access this server
resource Server_Name_AllowClientIP 'Microsoft.Sql/servers/firewallRules@2015-05-01-preview' = {
name: '${Server_Name_resource.name}/AllowClientIP'
properties: {
endIpAddress: clientIP
startIpAddress: clientIP
}
}
// Make the user an Azure AD administrator for the server, so that the user can connect with universal authentication
resource Server_Name_activeDirectory 'Microsoft.Sql/servers/administrators@2019-06-01-preview' = {
name: '${Server_Name_resource.name}/activeDirectory'
properties: {
administratorType: 'ActiveDirectory'
login: userName
//sid: reference(resourceId('Microsoft.Sql/servers', '${projectName}server'), '2019-06-01-preview', 'Full').identity.principalId
sid: userObjectId
//tenantId: AAD_TenantId //optional
}
}
//////////////////////////////////////////////////////////////////////////////
// Create the ContosoHR database using the DC-series hardware configuration //
//////////////////////////////////////////////////////////////////////////////
resource Database_Resource 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
name: '${Server_Name_resource.name}/ContosoHR'
location: location
tags: {}
sku: {
name: 'GP_DC_2'
tier: 'GeneralPurpose'
}
properties: {}
}
///////////////////////////////////////
// Configure an attestation provider //
///////////////////////////////////////
// Create the attestation provider
resource attestationProviderName_resource 'Microsoft.Attestation/attestationProviders@2020-10-01' = {
name: '${projectName}attest'
location: location
properties: {}
}
///////////////////////////////////
// Configure the web application //
///////////////////////////////////
// Create an App Service plan
resource WebAppServicePlan_Resource 'Microsoft.Web/serverfarms@2021-01-01' = {
name: '${projectName}plan'
location: location
properties: {}
sku: {
name: 'B1'
}
}
// Create the App Service
resource WebApp_Resource 'Microsoft.Web/sites@2021-01-01' = {
name: '${projectName}app'
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: WebAppServicePlan_Resource.id
}
//Set the database connection string for the application
resource WebAppConnectionString_Resource 'config' = {
name: 'connectionstrings'
properties: {
ContosoHRDatabase: {
value: 'Server=tcp:${Server_Name_resource.name}.database.windows.net;Database=ContosoHR;Column Encryption Setting=Enabled; Attestation Protocol = AAS; Enclave Attestation Url=${attestationProviderName_resource.properties.attestUri}; Authentication=Active Directory Managed Identity'
type: 'SQLAzure'
}
}
}
//Define AppSetting to fetch the correct project from the GitHub Repository
resource AppSetting 'config' = {
name: 'appsettings'
properties: {
PROJECT: 'samples/features/security/always-encrypted-with-secure-enclaves/source/ContosoHR/ContosoHR.csproj'
}
}
}
// Deploy the application
resource sourceControl 'Microsoft.Web/sites/sourcecontrols@2021-01-01' = {
name: '${projectName}app/web'
properties: {
repoUrl: 'https://github.com/microsoft/sql-server-samples.git'
branch: 'master'
isManualIntegration: true
}
dependsOn: [
Server_Name_resource
]
}
//////////////////////////////////////
// Create and configure a key vault //
//////////////////////////////////////
// Create a key vault and assign key permissions to the user, so that the user can manage the keys
resource KeyVault_Resource 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: '${projectName}vault'
location: location
tags: {}
properties: {
tenantId: subscription().tenantId
sku: {
family: 'A'
name: 'standard'
}
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: userObjectId
permissions: {
keys: [
'unwrapKey'
'wrapKey'
'verify'
'sign'
'get'
'list'
'create'
'delete'
'purge'
]
}
}
]
}
}
// Assign key permissions to the web app
resource KeyVaultWebAppAccessPolicy_Resource 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = {
name: any('${KeyVault_Resource.name}/add')
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
// objectId: reference(resourceId('Microsoft.Web/sites', '${projectName}app'), '2020-12-01', 'Full').resourceId
objectId: WebApp_Resource.identity.principalId
permissions: {
keys: [
'unwrapKey'
'verify'
'get'
]
}
}
]
}
}
// Create a key
resource Key_Resource 'Microsoft.KeyVault/vaults/keys@2019-09-01' = {
name: '${KeyVault_Resource.name}/CMK'
tags: {}
properties: {
attributes: {
enabled: true
}
kty: 'RSA'
keySize: 4096
}
}
@@ -0,0 +1,23 @@
Import-Module "Az.Resources"
######################################################################
# Prompt the user to enter the values of deployment parameters
######################################################################
$resourceGroupName = Read-Host -Prompt "Enter the resource group name"
$subscriptionId = Read-Host -Prompt "Enter your subscription id"
######################################################################
# Sign in to Azure
######################################################################
Connect-AzAccount
$context = Set-AzContext -Subscription $subscriptionId
######################################################################
# Delete the resource group and the key vault with purge proteciton on
######################################################################
$location = (Get-AzResourceGroup -Name $resourceGroupName).Location
Remove-AzResourceGroup -Name $resourceGroupName
Remove-AzKeyVault -VaultName "${resourceGroupName}vault" -InRemovedState -Location $location
@@ -0,0 +1,158 @@
Import-Module "Az" -MinimumVersion "5.6"
Import-Module "Az.Attestation" -MinimumVersion "0.1.8"
Import-Module "SqlServer" -MinimumVersion "21.1.18235"
######################################################################
# Prompt the user to enter the values of deployment parameters
######################################################################
$projectName = Read-Host -Prompt "Enter a project name that is used to generate resource names"
$subscriptionId = Read-Host -Prompt "Enter your subscription id"
$location = Read-Host -Prompt "Enter a region where you want to deploy the demo environment"
$sqlAdminUserName = Read-Host -Prompt "Enter the username of the Azure SQL database server administrator for SQL authentication"
$sqlAdminPasswordSecureString = Read-Host -Prompt "Enter the password of the Azure SQL database server administrator for SQL authentication" -AsSecureString
$sqlAdminPassword = (New-Object PSCredential "user",$sqlAdminPasswordSecureString).GetNetworkCredential().Password
$clientIP = (Invoke-WebRequest ifconfig.me/ip).Content.Trim()
$bicepFile = "azuredeploy.bicep"
$projectName = $projectName.ToLower()
######################################################################
# Sign in to Azure
######################################################################
Connect-AzAccount
$context = Set-AzContext -Subscription $subscriptionId
$userName = $context.Account.Id
$userObjectId = $(Get-AzADUser -UserPrincipalName $userName).Id
######################################################################
# Create a resource group
######################################################################
$resourceGroupName = "${projectName}"
New-AzResourceGroup -Name $resourceGroupName -Location $location
######################################################################
# Deploy the resources for the demo environment
######################################################################
New-AzResourceGroupDeployment `
-ResourceGroupName $resourceGroupName `
-TemplateFile $bicepFile `
-projectName $projectName `
-userObjectId $userObjectId `
-userName $userName `
-sqlAdminUserName $sqlAdminUserName `
-sqlAdminPassword $sqlAdminPassword `
-clientIP $clientIP
######################################################################
# Populate the database with data
######################################################################
$serverName = "${projectName}server.database.windows.net"
$databaseName = "ContosoHR"
$queryFile = "PopulateDatabase.sql"
$query = Get-Content -path $queryFile -Raw
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$serverName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
######################################################################
# Configure an extended event session to intercept application queries
######################################################################
$queryFile = "CreateXESession.sql"
$query = Get-Content -path $queryFile -Raw
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$serverName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
######################################################################
# Grant the web application access to the database
######################################################################
# Create a shadow principal, representing the application, in the database
$appName = "${projectName}app"
$query = "CREATE USER [$appName] FROM EXTERNAL PROVIDER;"
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$ServerName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
# Grant the application read access to the database.
$query = "EXEC sp_addrolemember 'db_datareader', '$appName';"
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$serverName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
# Grant the application write access to the database.
$query = "EXEC sp_addrolemember 'db_datawriter', '$appName';"
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$serverName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
# Grant the application access to the key metadata database.
$query = "GRANT VIEW ANY COLUMN MASTER KEY DEFINITION TO [$appName];"
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$serverName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
# Grant the application access to the key metadata database.
$query = "GRANT VIEW ANY COLUMN ENCRYPTION KEY DEFINITION TO [$appName];"
$accessToken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -ServerInstance "tcp:$serverName" -Database $databaseName -AccessToken $accessToken -QueryTimeout 30 -Query $query
######################################################################
# Configure key metadata in the database
######################################################################
# Get the column master key from Azure Key Vault
$keyVaultName = "${projectName}vault"
$keyName = "CMK"
$key = Get-AzKeyVaultKey -VaultName $keyVaultName -Name $keyName
# Connect to the database using the SqlServer PowerShell module
$connStr = "Data Source=tcp:$serverName;Initial Catalog=$databaseName;User ID=$sqlAdminUserName;Password=$sqlAdminPassword"
$database = Get-SqlDatabase -ConnectionString $connStr
# Sign in to Azure with your email address using the SqlServer PowerShell module
Add-SqlAzureAuthenticationContext -Interactive
# Create a column master key metadata object in the database for the key in Azure Key Vault
$cmkName = "CMK1"
$cmkSettings = New-SqlAzureKeyVaultColumnMasterKeySettings -KeyURL $key.Key.Kid -AllowEnclaveComputations
New-SqlColumnMasterKey -Name $cmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings
# Create a column encryption key and its metadata object in the database
$cekName = "CEK1"
New-SqlColumnEncryptionKey -Name $cekName -InputObject $database -ColumnMasterKey $cmkName
######################################################################
# Encrypt database columns
######################################################################
$encryptedColumnSettings = @()
$encryptedColumnSettings += New-SqlColumnEncryptionSettings -ColumnName "dbo.Employees.SSN" -EncryptionType "Randomized" -EncryptionKey $cekName
$encryptedColumnSettings += New-SqlColumnEncryptionSettings -ColumnName "dbo.Employees.Salary" -EncryptionType "Randomized" -EncryptionKey $cekName
Set-SqlColumnEncryption -ColumnEncryptionSettings $encryptedColumnSettings -InputObject $database -LogFileDirectory .
######################################################################
# Configure the attestation policy
######################################################################
$resourceGroupName = "${projectName}"
$attestationProviderName = "${projectName}attest"
$policyFile = "AttestationPolicy.txt"
$teeType = "SgxEnclave"
$policyFormat = "Text"
$policy=Get-Content -path $policyFile -Raw
Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $resourceGroupName -Tee $teeType -Policy $policy -PolicyFormat $policyFormat
# Get the attestation URL
$attestationProvider = Get-AzAttestation -Name $attestationProviderName -ResourceGroupName $resourceGroupName
$attestationUrl = $attestationProvider.AttestUri
######################################################################
# Print parameters for the demo
######################################################################
$app = Get-AzWebApp -Name $appName -ResourceGroupName $resourceGroupName
Write-Host -ForegroundColor "green" "Resource group name: $resourceGroupName"
Write-Host -ForegroundColor "green" "Database server name: $serverName"
Write-Host -ForegroundColor "green" "Database name: $databaseName"
Write-Host -ForegroundColor "green" "Attestation URL: $attestationUrl"
Write-Host -ForegroundColor "green" "Application URL: https://$($app.HostNames[0].ToString())"
@@ -0,0 +1,17 @@
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [SSN] [char](11) NOT NULL
WITH (ONLINE = ON)
GO
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [Salary] [Money] NOT NULL
WITH (ONLINE = ON)
GO
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [LastName] [nvarchar](50) NOT NULL
WITH (ONLINE = ON)
GO
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO
@@ -0,0 +1,14 @@
-- Data Owner's script - the user needs access to the keys
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [SSN] [char](11) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL;
GO
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [Salary] [Money]
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL;
GO
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO
@@ -0,0 +1,18 @@
SELECT * FROM [dbo].[Employees];
DECLARE @SSN CHAR(11) = '795-73-9838'
SELECT * FROM [dbo].[Employees] WHERE [SSN] = @SSN;
GO
DECLARE @SSNPattern CHAR(11) = '%9838'
SELECT * FROM [dbo].[Employees] WHERE [SSN] LIKE @SSNPattern;
GO
DECLARE @MinSalary MONEY = 40000
DECLARE @MaxSalary MONEY = 45000
SELECT * FROM [dbo].[Employees] WHERE [Salary] > @MinSalary AND [Salary] < @MaxSalary;
GO
DECLARE @LastNamePrefix NVARCHAR(50) = 'Aber%';
SELECT * FROM [dbo].[Employees] WHERE [LastName] LIKE @LastNamePrefix;
GO
@@ -0,0 +1,55 @@
DECLARE @ExtendedEventsSessionName sysname = N'Demo';
DECLARE @StartTime datetimeoffset;
DECLARE @EndTime datetimeoffset;
DECLARE @Offset int;
DROP TABLE IF EXISTS #xmlResults;
CREATE TABLE #xmlResults
(
xeTimeStamp datetimeoffset NOT NULL
, xeXML XML NOT NULL
);
SET @StartTime = DATEADD(HOUR, -4, GETDATE()); --modify this to suit your needs
SET @EndTime = GETDATE();
SET @Offset = DATEDIFF(MINUTE, GETDATE(), GETUTCDATE());
SET @StartTime = DATEADD(MINUTE, @Offset, @StartTime);
SET @EndTime = DATEADD(MINUTE, @Offset, @EndTime);
DECLARE @target_data xml;
SELECT @target_data = CONVERT(xml, target_data)
FROM sys.dm_xe_database_sessions AS s
JOIN sys.dm_xe_database_session_targets AS t
ON t.event_session_address = s.address
WHERE s.name = @ExtendedEventsSessionName
AND t.target_name = N'ring_buffer';
;WITH src AS
(
SELECT xeXML = xm.s.query('.')
FROM @target_data.nodes('/RingBufferTarget/event') AS xm(s)
)
INSERT INTO #xmlResults (xeXML, xeTimeStamp)
SELECT src.xeXML
, [xeTimeStamp] = src.xeXML.value('(/event/@timestamp)[1]', 'datetimeoffset(7)')
FROM src;
DECLARE @xe xml;
SELECT * FROM (
SELECT
[TimeStamp] = CONVERT(varchar(30), DATEADD(MINUTE, 0 - @Offset, xr.xeTimeStamp), 120)
, [Query] = xr.xeXML.query (N'/event/data[11]/value')
FROM #xmlResults xr
WHERE xr.xeTimeStamp >= @StartTime
AND xr.xeTimeStamp<= @EndTime
) AS [t]
WHERE
CONVERT(nvarchar(max), Query) LIKE '%Employees%'
AND CONVERT(nvarchar(max), Query) LIKE '%SSN%'
AND CONVERT(nvarchar(max), Query) NOT LIKE '%sp_describe_parameter_encryption%'
AND CONVERT(nvarchar(max), Query) NOT LIKE '%COUNT%'
ORDER BY [TimeStamp] DESC
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2036
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContosoClinic", "ContosoClinic\ContosoClinic.csproj", "{CD00D85A-BCAC-4784-AAEE-F8A04F671168}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContosoHR", "ContosoHR\ContosoHR.csproj", "{91C0C152-3656-4460-8C0C-DAE0505E26FC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -11,15 +11,15 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CD00D85A-BCAC-4784-AAEE-F8A04F671168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD00D85A-BCAC-4784-AAEE-F8A04F671168}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD00D85A-BCAC-4784-AAEE-F8A04F671168}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD00D85A-BCAC-4784-AAEE-F8A04F671168}.Release|Any CPU.Build.0 = Release|Any CPU
{91C0C152-3656-4460-8C0C-DAE0505E26FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91C0C152-3656-4460-8C0C-DAE0505E26FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91C0C152-3656-4460-8C0C-DAE0505E26FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91C0C152-3656-4460-8C0C-DAE0505E26FC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1426E8BB-D281-42C3-8732-F25D28B3F7B0}
SolutionGuid = {09B03C29-04E6-4C93-9C58-C16B6A907DAE}
EndGlobalSection
EndGlobal
@@ -0,0 +1,5 @@
{
"version": 1,
"isRoot": true,
"tools": {}
}
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>54e8ce16-db31-4c81-a83f-e7f742cb59d9</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="3.0.0" />
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.9" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.10" />
</ItemGroup>
</Project>
@@ -0,0 +1,99 @@
using ContosoHR.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using Microsoft.EntityFrameworkCore;
using System.Data;
using Microsoft.Data.SqlClient;
using System.Globalization;
namespace ContosoHR.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly ContosoHRContext context;
public EmployeeController(ContosoHRContext context)
{
this.context = context;
}
[HttpPost]
public IActionResult GetEmployees()
{
try
{
var draw = Request.Form["draw"].FirstOrDefault();
var start = Request.Form["start"].FirstOrDefault();
var length = Request.Form["length"].FirstOrDefault();
var sortColumn = Request.Form["columns[" + Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault();
var sortColumnDirection = Request.Form["order[0][dir]"].FirstOrDefault();
var searchValue = Request.Form["search[value]"].FirstOrDefault();
int pageSize = length != null ? Convert.ToInt32(length) : 0;
int skip = start != null ? Convert.ToInt32(start) : 0;
int recordsTotal = 0;
string salaryRange = Request.Form["columns[4][search][value]"]; // NOTE: it must match .column(8) in Index.cshtml
int from = 0, to = 100000;
if (!string.IsNullOrEmpty(salaryRange))
{
from = Convert.ToInt32(salaryRange.Split(':')[0]);
to = Convert.ToInt32(salaryRange.Split(':')[1]);
}
var ssnSearchPattern = new SqlParameter();
ssnSearchPattern.ParameterName = @"@SSNSearchPattern";
ssnSearchPattern.DbType = DbType.AnsiStringFixedLength;
ssnSearchPattern.Direction = ParameterDirection.Input;
ssnSearchPattern.Value = "%" + searchValue + "%";
ssnSearchPattern.Size = ssnSearchPattern.Value.ToString().Length;
var nameSearchPattern = new SqlParameter();
nameSearchPattern.ParameterName = @"@NameSearchPattern";
nameSearchPattern.DbType = DbType.String;
nameSearchPattern.Direction = ParameterDirection.Input;
nameSearchPattern.Value = "%" + searchValue + "%";
nameSearchPattern.Size = nameSearchPattern.Value.ToString().Length;
var minSalary = new SqlParameter();
minSalary.ParameterName = @"@MinSalary";
minSalary.DbType = DbType.Currency;
minSalary.Direction = ParameterDirection.Input;
minSalary.Value = from;
var maxSalary = new SqlParameter();
maxSalary.ParameterName = @"@MaxSalary";
maxSalary.DbType = DbType.Currency;
maxSalary.Direction = ParameterDirection.Input;
maxSalary.Value = to;
var employeeData = context.Employees.FromSqlRaw(
@"SELECT [EmployeeID], [SSN], [FirstName], [LastName], [Salary] FROM [dbo].[Employees] WHERE ([SSN] LIKE @SSNSearchPattern OR [LastName] LIKE @NameSearchPattern) AND [Salary] BETWEEN @MinSalary AND @MaxSalary"
, ssnSearchPattern
, nameSearchPattern
, minSalary
, maxSalary);
if (!(string.IsNullOrEmpty(sortColumn) && string.IsNullOrEmpty(sortColumnDirection)))
{
employeeData = employeeData.OrderBy(sortColumn + " " + sortColumnDirection);
}
recordsTotal = employeeData.Count();
var data = employeeData.Skip(skip).Take(pageSize).ToList();
var jsonData = new { draw = draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = data };
return Ok(jsonData);
}
catch (Exception ex)
{
Console.Write(ex.Message);
return new JsonResult(new { error = ex.ToString() });
}
}
}
}
@@ -0,0 +1,18 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
#nullable disable
namespace ContosoHR.Models
{
public partial class ContosoHRContext : DbContext
{
public ContosoHRContext(DbContextOptions<ContosoHRContext> options)
: base(options)
{
}
public virtual DbSet<Employee> Employees { get; set; }
}
}
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
#nullable disable
namespace ContosoHR.Models
{
public partial class Employee
{
public int EmployeeId { get; set; }
public string Ssn { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public decimal Salary { get; set; }
}
}
@@ -0,0 +1,49 @@
@page
@model ContosoHR.Pages.Employees.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Employee</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Employee.Ssn" class="control-label"></label>
<input asp-for="Employee.Ssn" class="form-control" />
<span asp-validation-for="Employee.Ssn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Employee.FirstName" class="control-label"></label>
<input asp-for="Employee.FirstName" class="form-control" />
<span asp-validation-for="Employee.FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Employee.LastName" class="control-label"></label>
<input asp-for="Employee.LastName" class="form-control" />
<span asp-validation-for="Employee.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Employee.Salary" class="control-label"></label>
<input asp-for="Employee.Salary" class="form-control" />
<span asp-validation-for="Employee.Salary" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using ContosoHR.Models;
namespace ContosoHR.Pages.Employees
{
public class CreateModel : PageModel
{
private readonly ContosoHR.Models.ContosoHRContext _context;
public CreateModel(ContosoHR.Models.ContosoHRContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Employee Employee { get; set; }
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Employees.Add(Employee);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
@@ -0,0 +1,46 @@
@page
@model ContosoHR.Pages.Employees.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Employee</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.Ssn)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.Ssn)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.FirstName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.FirstName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.Salary)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.Salary)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Employee.EmployeeId" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ContosoHR.Models;
namespace ContosoHR.Pages.Employees
{
public class DeleteModel : PageModel
{
private readonly ContosoHR.Models.ContosoHRContext _context;
public DeleteModel(ContosoHR.Models.ContosoHRContext context)
{
_context = context;
}
[BindProperty]
public Employee Employee { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == id);
if (Employee == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Employee = await _context.Employees.FindAsync(id);
if (Employee != null)
{
_context.Employees.Remove(Employee);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
}
@@ -0,0 +1,43 @@
@page
@model ContosoHR.Pages.Employees.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Employee</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.Ssn)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.Ssn)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.FirstName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.FirstName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Employee.Salary)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee.Salary)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Employee.EmployeeId">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ContosoHR.Models;
namespace ContosoHR.Pages.Employees
{
public class DetailsModel : PageModel
{
private readonly ContosoHR.Models.ContosoHRContext _context;
public DetailsModel(ContosoHR.Models.ContosoHRContext context)
{
_context = context;
}
public Employee Employee { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == id);
if (Employee == null)
{
return NotFound();
}
return Page();
}
}
}
@@ -0,0 +1,50 @@
@page
@model ContosoHR.Pages.Employees.EditModel
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Employee</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Employee.EmployeeId" />
<div class="form-group">
<label asp-for="Employee.Ssn" class="control-label"></label>
<input asp-for="Employee.Ssn" class="form-control" />
<span asp-validation-for="Employee.Ssn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Employee.FirstName" class="control-label"></label>
<input asp-for="Employee.FirstName" class="form-control" />
<span asp-validation-for="Employee.FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Employee.LastName" class="control-label"></label>
<input asp-for="Employee.LastName" class="form-control" />
<span asp-validation-for="Employee.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Employee.Salary" class="control-label"></label>
<input asp-for="Employee.Salary" class="form-control" />
<span asp-validation-for="Employee.Salary" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using ContosoHR.Models;
namespace ContosoHR.Pages.Employees
{
public class EditModel : PageModel
{
private readonly ContosoHR.Models.ContosoHRContext _context;
public EditModel(ContosoHR.Models.ContosoHRContext context)
{
_context = context;
}
[BindProperty]
public Employee Employee { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == id);
if (Employee == null)
{
return NotFound();
}
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Employee).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EmployeeExists(Employee.EmployeeId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool EmployeeExists(int id)
{
return _context.Employees.Any(e => e.EmployeeId == id);
}
}
}
@@ -0,0 +1,54 @@
@page
@model ContosoHR.Pages.Employees.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Employee[0].Ssn)
</th>
<th>
@Html.DisplayNameFor(model => model.Employee[0].FirstName)
</th>
<th>
@Html.DisplayNameFor(model => model.Employee[0].LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.Employee[0].Salary)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Employee) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Ssn)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.EmployeeId">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.EmployeeId">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.EmployeeId">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ContosoHR.Models;
namespace ContosoHR.Pages.Employees
{
public class IndexModel : PageModel
{
private readonly ContosoHR.Models.ContosoHRContext _context;
public IndexModel(ContosoHR.Models.ContosoHRContext context)
{
_context = context;
}
public IList<Employee> Employee { get;set; }
public async Task OnGetAsync()
{
Employee = await _context.Employees.ToListAsync();
}
}
}
@@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoHR.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}
@@ -0,0 +1,39 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<link href="~/lib/datatables/css/dataTables.bootstrap4.min.css" rel="stylesheet" />
<link href="~/lib/jqueryui/jquery-ui.css" rel="stylesheet" />
@section Scripts
{
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jqueryui/jquery-ui.js"></script>
<script src="~/lib/datatables/js/jquery.dataTables.min.js"></script>
<script src="~/lib/datatables/js/dataTables.bootstrap4.min.js"></script>
<script src="~/js/employeeDatatable.js"></script>
}
<p>
<label for="amount">Salary range:</label>
<input type="text" id="amount" readonly style="border:0; color:#f6931f; font-weight:bold;">
</p>
<p>
<div id="slider-range"></div>
<br />
</p>
<div class="container">
<br />
<div style="width:100%; margin:0 auto;">
<table id="employeeDatatable" class="table table-striped table-bordered table-sm dt-responsive nowrap" width="100%" cellspacing="0">
<thead>
<tr>
<th>EmployeeId</th>
<th>Ssn</th>
<th>First Name</th>
<th>Last Name</th>
<th>Salary</th>
</tr>
</thead>
</table>
</div>
</div>
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoHR.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
@@ -0,0 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoHR.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso HR</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">Contoso HR</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Employees/Index">Employees</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2021 - Contoso HR - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
@@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@@ -0,0 +1,3 @@
@using ContosoHR
@namespace ContosoHR.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace ContosoHR
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
@@ -0,0 +1,113 @@
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_dependencyType": "appService.windows"
},
"parameters": {
"resourceGroupName": {
"type": "string",
"defaultValue": "aeenclavedemowus",
"metadata": {
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
}
},
"resourceGroupLocation": {
"type": "string",
"defaultValue": "westus",
"metadata": {
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
}
},
"resourceName": {
"type": "string",
"defaultValue": "ContosoHR20210131163006",
"metadata": {
"description": "Name of the main resource to be created by this template."
}
},
"resourceLocation": {
"type": "string",
"defaultValue": "[parameters('resourceGroupLocation')]",
"metadata": {
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
}
}
},
"variables": {
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"name": "[parameters('resourceGroupName')]",
"location": "[parameters('resourceGroupLocation')]",
"apiVersion": "2019-10-01"
},
{
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
"resourceGroup": "[parameters('resourceGroupName')]",
"apiVersion": "2019-10-01",
"dependsOn": [
"[parameters('resourceGroupName')]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"location": "[parameters('resourceLocation')]",
"name": "[parameters('resourceName')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"tags": {
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
},
"dependsOn": [
"[variables('appServicePlan_ResourceId')]"
],
"kind": "app",
"properties": {
"name": "[parameters('resourceName')]",
"kind": "app",
"httpsOnly": true,
"reserved": false,
"serverFarmId": "[variables('appServicePlan_ResourceId')]",
"siteConfig": {
"metadata": [
{
"name": "CURRENT_STACK",
"value": "dotnetcore"
}
]
}
},
"identity": {
"type": "SystemAssigned"
}
},
{
"location": "[parameters('resourceLocation')]",
"name": "[variables('appServicePlan_name')]",
"type": "Microsoft.Web/serverFarms",
"apiVersion": "2015-08-01",
"sku": {
"name": "S1",
"tier": "Standard",
"family": "S",
"size": "S1"
},
"properties": {
"name": "[variables('appServicePlan_name')]"
}
}
]
}
}
}
]
}
@@ -0,0 +1,113 @@
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_dependencyType": "appService.windows"
},
"parameters": {
"resourceGroupName": {
"type": "string",
"defaultValue": "aeenclavedemo2",
"metadata": {
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
}
},
"resourceGroupLocation": {
"type": "string",
"defaultValue": "westus",
"metadata": {
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
}
},
"resourceName": {
"type": "string",
"defaultValue": "aeenclavedemo2app",
"metadata": {
"description": "Name of the main resource to be created by this template."
}
},
"resourceLocation": {
"type": "string",
"defaultValue": "[parameters('resourceGroupLocation')]",
"metadata": {
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
}
}
},
"variables": {
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"name": "[parameters('resourceGroupName')]",
"location": "[parameters('resourceGroupLocation')]",
"apiVersion": "2019-10-01"
},
{
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
"resourceGroup": "[parameters('resourceGroupName')]",
"apiVersion": "2019-10-01",
"dependsOn": [
"[parameters('resourceGroupName')]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"location": "[parameters('resourceLocation')]",
"name": "[parameters('resourceName')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"tags": {
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
},
"dependsOn": [
"[variables('appServicePlan_ResourceId')]"
],
"kind": "app",
"properties": {
"name": "[parameters('resourceName')]",
"kind": "app",
"httpsOnly": true,
"reserved": false,
"serverFarmId": "[variables('appServicePlan_ResourceId')]",
"siteConfig": {
"metadata": [
{
"name": "CURRENT_STACK",
"value": "dotnetcore"
}
]
}
},
"identity": {
"type": "SystemAssigned"
}
},
{
"location": "[parameters('resourceLocation')]",
"name": "[variables('appServicePlan_name')]",
"type": "Microsoft.Web/serverFarms",
"apiVersion": "2015-08-01",
"sku": {
"name": "S1",
"tier": "Standard",
"family": "S",
"size": "S1"
},
"properties": {
"name": "[variables('appServicePlan_name')]"
}
}
]
}
}
}
]
}
@@ -0,0 +1,95 @@
using ContosoHR.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
using Azure.Core;
using Azure.Identity;
namespace ContosoHR
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
InitializeAzureKeyVaultProvider();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
string s = Configuration.GetConnectionString("ContosoHRDatabase");
services.AddDbContext<ContosoHRContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ContosoHRDatabase")));
services.AddControllers();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
// Initialize the Azure Key Vault provider for Always Encrypted. Required if column master keys are stored in Azure Key Vault.
private void InitializeAzureKeyVaultProvider()
{
TokenCredential tokenCredential = null;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("ContosoHRDatabase"));
if (builder.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)
{
// If the application uses a managed identity to talk to Azure SQL Database, use the managed identity for Azure Key Vault too.
tokenCredential = new ManagedIdentityCredential();
}
else {
// Assume a managed identity is not available to the app. Instead, use a client id/secret to authenticate to Azure Key Vault.
// Fetch client id, secret, tenant id from the configuration.
// It is recommended you specify these parameters in secrets.json.
// See https://docs.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-5.0&tabs=windows on how store secrets in secrets.json.
var clientId = Configuration["ClientId"];
var secret = Configuration["Secret"];
var tenantId = Configuration["TenantId"];
tokenCredential = new ClientSecretCredential(tenantId, clientId, secret);
}
SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider =
new SqlColumnEncryptionAzureKeyVaultProvider(tokenCredential);
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(
customProviders: new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider}
}
);
}
}
}
@@ -0,0 +1,10 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ContosoHRDatabase": "Server=tcp:<yourservername>.database.windows.net;Database=ContosoHR;Column Encryption Setting=Enabled; Attestation Protocol = AAS; Enclave Attestation Url=<yourattestationurl>; Authentication=Active Directory Managed Identity"
}
}
@@ -0,0 +1,14 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "datatables@1.10.21",
"destination": "wwwroot/lib/datatables/"
},
{
"library": "jqueryui@1.12.1",
"destination": "wwwroot/lib/jqueryui/"
}
]
}
@@ -0,0 +1,71 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px; /* Vertically center the text there */
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -0,0 +1,46 @@
$(document).ready(function () {
var table = $("#employeeDatatable").DataTable({
"processing": true,
"serverSide": true,
"filter": true,
"ajax": {
"url": "/api/employee",
"type": "POST",
"datatype": "json"
},
"columnDefs": [
{
"targets": [0],
"visible": false,
"searchable": false
}
],
"columns": [
{ "data": "employeeId", "name": "EmployeeId", "autoWidth": true },
{ "data": "ssn", "name": "Ssn", "autoWidth": true },
{ "data": "firstName", "name": "FirstName", "autoWidth": true },
{ "data": "lastName", "name": "LastName", "autoWidth": true },
{ "data": "salary", "name": "Salary", render: $.fn.dataTable.render.number(',', '.',0), "autoWidth": true }
]
});
$(function () {
$("#slider-range").slider({
range: true,
min: 0,
max: 100000,
values: [0, 100000],
slide: function (event, ui) {
$("#amount").val("$" + ui.values[0] + " - $" + ui.values[1]);
var from = ui.values[0];
var to = ui.values[1];
table
.column(4) //---> THIS IS ID OF THE Salary column
.search(from + ":" + to)
.draw();
}
});
$("#amount").val("$" + $("#slider-range").slider("values", 0) +
" - $" + $("#slider-range").slider("values", 1));
});
});
@@ -0,0 +1,4 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2018 Twitter, Inc.
Copyright (c) 2011-2018 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

Some files were not shown because too many files have changed in this diff Show More