Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SELECT
DB_NAME(drs.database_id) AS DatabaseName,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.is_commit_participant,
drs.log_send_queue_size,
drs.redo_queue_size,
drs.last_commit_time
FROM sys.dm_hadr_database_replica_states drs
WHERE DB_NAME(drs.database_id) LIKE 'TEAM01%'
ORDER BY DatabaseName;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/************************************************************************************************************************************
*
* ================
* MONITORING LAB
* ================

* The databases are hosted on:
*
* sqlhackmiXXXXX.database.windows.net
*
************************************************************************************************************************************/




/************************************************************************************************************************************
* PART 1: USING [sys].[dm_db_resource_stats] DMV TO LOOK AT THE SQL MI RESOURCES STATS
************************************************************************************************************************************/

--QUERY 1
SELECT * FROM sys.dm_db_resource_stats ; -- measure each 15 seconds over 1h
/*
* Note the [avg_cpu_percent] is above 80% indicating that the CPU could be under pressure.
*/
--QUERY 2
SELECT end_time,avg_cpu_percent,avg_data_io_percent FROM sys.dm_db_resource_stats order by end_time asc;

-- QUERY 3
-- For a wide historic up to 14 days
select start_time,avg_cpu_percent,io_requests,* from sys.server_resource_stats

/*
* END PART 1 - RETURN TO LAB INSTRUCTIONS
*/

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* ================
* MONITORING LAB
* ================

* The databases are hosted on:
*
* sqlhackmiXXXXX.database.windows.net
*
************************************************************************************************************************************/


/************************************************************************************************************************************
* PART 2: USING [sys].[dm_exec_procedure_stats] TO EXAMINE EXECUTION STATS
************************************************************************************************************************************/

--This query uses the [sys].[dm_exec_procedure_stats] DMV identify most consuming stored procedures and the details
-- QUERY 1
select OBJECT_NAME(object_id,database_id) as ProcedureName
,total_worker_time/1000 as total_worker_time_ms,min_worker_time/1000 as min_worker_time,max_worker_time/1000 as max_worker_time_ms,(total_worker_time/execution_count)/1000 as avg_worker_time_ms
,total_elapsed_time/1000 as total_elapsed_time_ms,min_elapsed_time/1000 as min_elapsed_time_ms,max_elapsed_time/1000 as max_elapsed_time_ms,(total_elapsed_time/execution_count)/1000 as avg_execution_time_ms
,total_logical_reads,min_logical_reads,max_logical_reads,(total_logical_reads/execution_count) as avg_logical_reads
,total_physical_reads,min_physical_reads,max_physical_reads,(total_physical_reads/execution_count) as avg_physical_reads
,total_logical_writes,min_logical_writes,max_logical_writes,(total_logical_writes/execution_count) as avg_logical_writes,type,cached_time,last_execution_time,execution_count
from sys.dm_exec_procedure_stats s
Where OBJECT_NAME(object_id,database_id) is not NULL
order by s.total_worker_time desc

-- QUERY 2 replace the filter clause with the SP identify
-- Procedure statement details
select OBJECT_NAME(object_id,database_id) as ProcedureName,st.text
,SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2) + 1) AS statement_text
,qp.query_plan
,qs.execution_count
,qs.total_worker_time,qs.min_worker_time,qs.max_worker_time,(qs.total_worker_time/qs.execution_count) as avg_worker_time
,qs.total_elapsed_time,qs.min_elapsed_time,qs.max_elapsed_time,(qs.total_elapsed_time/qs.execution_count) as avg_execution_time
,qs.total_logical_reads,qs.min_logical_reads,qs.max_logical_reads,(qs.total_logical_reads/qs.execution_count) as avg_logical_reads
,qs.total_physical_reads,qs.min_physical_reads,qs.max_physical_reads,(qs.total_physical_reads/qs.execution_count) as avg_physical_reads
,qs.total_logical_writes,qs.min_logical_writes,qs.max_logical_writes,(qs.total_logical_writes/qs.execution_count) as avg_logical_writes

,*
from sys.dm_exec_procedure_stats ps
left join sys.dm_exec_query_stats qs
on ps.sql_handle=qs.sql_handle
cross apply sys.dm_exec_sql_text(qs.sql_handle) st
cross apply sys.dm_exec_query_plan(qs.plan_handle) qp
where OBJECT_NAME(object_id,database_id) ='MyProc'
order by ProcedureName,qs.total_worker_time desc
/*
* END PART 2 - RETURN TO LAB INSTRUCTIONS
*/

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
--Advanced Threat Protection
DECLARE @UserInput nvarchar(max) = ''' OR 1=1--';
DECLARE @SQL nvarchar(max) = 'SELECT * FROM sys.databases WHERE name = ''' + @UserInput + '''';
EXEC(@SQL);
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
--- **** REPLACE TEAMXX with your TEAM number e.g TEAM01 **----

--1. Create an audit for your Managed Instance and define the target
-- Create the server audit.
-- Change the path to a path to Blob Storage Path for auditing
USE MASTER
GO
CREATE SERVER AUDIT [TEAMXX_server_audit]
TO URL(PATH ='https://sqlhacksapfu2ietzqrxem.blob.core.windows.net/auditlogs')
GO


--2. Create a database audit specification that maps to the audit by running the following query in Management Studio

USE [TEAMXX_TenantDataDb]
GO
-- Create the database audit specification.
CREATE DATABASE AUDIT SPECIFICATION TEAMXX_Audit_Data --change xx to team number
FOR SERVER AUDIT [TEAMXX_server_audit] --change to server audit name created in step 1
ADD (INSERT, UPDATE, DELETE, SELECT
ON UserTransactions BY dbo ) --[UserTransactions] is the target table we're going to audit
WITH (STATE = ON);
GO

/**
--- You will need to configure blob storage for audit logs using the SQL Server Management Studio (SSMS) 18 (Preview):
-- 1. Connect to the managed instance using SQL Server Management Studio (SSMS) UI
-- 2. Expand the Security node, right-click on the Audits node, and click on "TEAMXX_server_audit"
-- 3. Make sure "URL" is selected in Audit destination and click on Browse:
-- 4. Sign in to your Azure account
-- 5. Select your subscription, sqlhacksapfu2ietzqrxem, and sqlaudits from the dropdowns. Once you have finished click OK.
**/


-- enable the audit
-- ensure you have connected to the audit log file location

USE MASTER
GO
ALTER SERVER AUDIT [TEAMXX_server_audit] WITH (STATE = ON)

--3. Run a couple of SELECT and DELETE Statements on TEAMXX_TenantDataDb which you have enabled.

-- Run select statements and delete statments below:
USE [TEAMXX_TenantDataDb]
GO
SELECT * FROM TEAMXX_TenantDataDb.dbo.usertransactions
WHERE UserTranID < 1000

SELECT * FROM TEAMXX_TenantDataDb.dbo.usertransactions
WHERE UserTranID > 1000

-- delete transaction
DELETE FROM TEAMXX_TenantDataDb.dbo.usertransactions
WHERE UserTranID = 1000


--4. Read the audit events by using the fn_get_audit_file function. The directory hierarchy within the container is of the form <ServerName>/<DatabaseName>/<AuditName>/<Date>/.

-- check the audit logs
SELECT event_time,
action_id,
client_ip,
session_server_principal_name,
database_principal_name,
statement,
application_name,
data_sensitivity_information, --new column added to audit log
host_name
FROM sys.fn_get_audit_file ('https://sqlhacksapfu2ietzqrxem.blob.core.windows.net/auditlogs/sqlhackmi-pfu2ietzqrxem/master/', default, default) -- change the location to reflect the directory of the XEL file in your blob storage
WHERE data_sensitivity_information like '%confidential%'
AND database_name = 'TEAMXX_TenantDataDb'
GO-- change the location to reflect the directory of the XEL file in your blob storage
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- *** REPLACE XX WITH YOUR TEAM NUMBER ***---

--1. Run the below script to mask the transaction amount in the UserTransactions table using the default masking function
USE TEAMXX_TenantDataDB;
GO
ALTER TABLE [UserTransactions]
ALTER COLUMN [TranAmount] [decimal](18, 2) MASKED WITH (FUNCTION = 'DEFAULT()')


--3. Check to see masked columns
SELECT c.name, tbl.name as table_name, c.is_masked, c.masking_function
FROM sys.masked_columns AS c
JOIN sys.tables AS tbl
ON c.[object_id] = tbl.[object_id]
WHERE is_masked = 1;


--4. Check the table to ensure you are still able to see ALL the data
SELECT * FROM TEAMXX_TenantDataDB.dbo.UserTransactions
GO

--5. Create a user for the developer and grant the read only access on the UserTransactions table
CREATE USER Peter WITHOUT LOGIN
GRANT SELECT ON dbo.UserTransactions TO Peter;

--6. Run the query below to ensure we have been able to prevent the developer from accessing customers� privacy information. This will run the SELECT statement as the new user, Peter, which was created in the previous step
EXECUTE AS USER = 'Peter';
SELECT * FROM TEAMXX_TenantDataDb.dbo.UserTransactions;
REVERT;

--7. Grant Peter permission to Peter to see unmasked data
GRANT UNMASK TO Peter;
EXECUTE AS USER = 'Peter';
SELECT * FROM TEAMXX_TenantDataDb.dbo.UserTransactions;
REVERT;

--8. Clean up masked column
USE TEAMXX_TenantDataDB;
GO
ALTER TABLE [UserTransactions]
ALTER COLUMN [TranAmount] DROP MASKED;

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--Enable TDE for databases
USE [master]
GO
ALTER DATABASE [TEAMXX_LocalMasterDataDB] SET ENCRYPTION ON
GO

ALTER DATABASE [TEAMXX_SharedMasterDataDB] SET ENCRYPTION ON
GO

ALTER DATABASE [TEAMXX_TenantDataDB] SET ENCRYPTION ON
GO

-- ALTER LOGIN [$0] WITH PASSWORD = '<insert a strong password >'
ALTER LOGIN [TEAMXX] DISABLE;
-- DBA must change the password before re-enable.
ALTER LOGIN [teamXX] WITH PASSWORD = '<insert a strong password >'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Managed Identity must be enabled on the SQL Managed Instance for this to work
-- Configure the Managed Identity for the SQL Managed Instance to have access to the storage account (Storage Blob Data Contributor role) and the storage account must have a container named "auditlogs" created in it.
-- Please replace <storage-account> with the name of your storage account in the below script.

USE master;
CREATE CREDENTIAL [https://<storage-account>.blob.core.windows.net/auditlogs]
WITH IDENTITY='MANAGED IDENTITY'
GO

CREATE SERVER AUDIT [sqlmi_auditlog]
TO URL ( PATH ='https://<storage-account>.blob.core.windows.net/auditlogs'
, RETENTION_DAYS = 30 )
GO

ALTER SERVER AUDIT [sqlmi_auditlog]
WITH (STATE = ON)
GO
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
128 changes: 128 additions & 0 deletions 03-Azure/01-02 Data/02-SQL_Modernization/labautomation/deploy-lab.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<#
.SYNOPSIS
Deploys the lab resources scoped to a subscription or resource group.
.DESCRIPTION
Provides a controlled deployment flow for lab environments, optionally limited to a resource group and specific Entra user IDs.
.PARAMETER DeploymentType
Defines the deployment scope; allowed values are subscription or resourcegroup.
.PARAMETER SubscriptionId
Specifies the Azure subscription that contains the lab resources.
.PARAMETER ResourceGroupName
In case of resourcegroup deployment, specifies the target resource group name.
.PARAMETER PreferredLocation
Specifies the preferred Azure regions (ordered by preference) for resource deployment. An empty array indicates no preference.
.PARAMETER AllowedEntraUserIds
Optional list of Entra user object IDs permitted to access the lab resources.
#>
param(
[Parameter(Mandatory=$true)]
[ValidateSet('subscription','resourcegroup', 'resourcegroup-with-subscriptionowner')]
[string]$DeploymentType,

[Parameter(Mandatory=$true)]
[string]$SubscriptionId,

[string]$ResourceGroupName = "",

[string[]]$PreferredLocation = @(),

[string[]]$AllowedEntraUserIds = @()
)

$ErrorActionPreference = 'Stop'

$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
Write-Host "[$SubscriptionId] Running deploy-lab.ps1 in $scriptPath"

# Lab environment configuration
$environmentName = "sqlhack"
$sharedResourceGroupName = "rg-sqlhack-shared"
$adminUsername = "DemoUser"
$adminPassword = New-MhhStablePassword -Purpose 'vm-admin' -SubscriptionId $SubscriptionId -ResourceGroupName "Default"

# Template file path
$templatePath = Join-Path $scriptPath "infra"
$template = Join-Path $templatePath "main.bicep"
Write-Host "[$SubscriptionId] Deploying lab resources from template $template"

#if ($AllowedEntraUserIds.Count -eq 0) {
# throw "No Entra user object ID was provided. The MicroHack platform must pass at least one value in AllowedEntraUserIds."
#}

# set the effective location (example)
if($PreferredLocation.Count -gt 0) {
$effectiveLocation = $PreferredLocation[0]
} else {
$effectiveLocation = "swedencentral" # Default location if no preference is provided
}
$deploymentLocations = if ($PreferredLocation.Count -gt 0) { $PreferredLocation } else { @($effectiveLocation) }

# set the effective resource group based on deployment type (example)
if($DeploymentType -eq 'subscription') {
$stableHash = Get-MhhStableHash -Value $AllowedEntraUserIds -Length 24
$effectiveResourceGroup = "lab-$stableHash"
Write-Host "Deploying lab resources at the subscription level in subscription $SubscriptionId..."
if(-not (Get-AzResourceGroup -Name $effectiveResourceGroup -ErrorAction SilentlyContinue)) {
Write-Host "Creating resource group '$effectiveResourceGroup' in location '$effectiveLocation'..."
New-AzResourceGroup -Name $effectiveResourceGroup -Location $effectiveLocation -Verbose
} else {
Write-Host "Resource group '$effectiveResourceGroup' already exists."
}
}
else {
if ([string]::IsNullOrWhiteSpace($ResourceGroupName)) {
throw "ResourceGroupName is empty for deployment type '$DeploymentType'. The MicroHack platform must provide the participant resource group."
}
$effectiveResourceGroup = $ResourceGroupName
}

Write-Host "[$SubscriptionId] Deploying to user: $($AllowedEntraUserIds[0])"

$me = Get-MhhLabUser -UserId @($AllowedEntraUserIds)[0]
Write-Host "Deploying for $($me.UserPrincipalName) ($($me.ShortName))"
$vmPostfix = "$(($me.ShortName -split "\.")[0])"
if ($vmPostfix.Length -gt 12) {
$vmPostfix = $vmPostfix.Substring(0,12)
}
$vmPostfix = $vmPostfix -replace "_", "-"
$vmName = "VM-$vmPostfix"
$TeamName = $me.ShortName
$TeamName = $TeamName.ToUpper()
$legacySQLName = "legacySQL2016"


$storageAccountName = Get-AzStorageAccount -ResourceGroupName $sharedResourceGroupName -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty StorageAccountName
$managedInstanceFQDN = Get-AzSqlInstance -ResourceGroupName $sharedResourceGroupName -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullyQualifiedDomainName

Write-Host "[$SubscriptionId] Using storage account: $storageAccountName"
Write-Host "[$SubscriptionId] Using managed instance FQDN: $managedInstanceFQDN"
Write-Host "[$SubscriptionId] Using Computer Name: $vmName"

# Deploy lab resources using Invoke-MhhDeploymentWithRegionFallback
$result = Invoke-MhhDeploymentWithRegionFallback `
-PreferredLocations $deploymentLocations `
-ResourceGroupName $effectiveResourceGroup `
-RgOwnerEntraObjectIds $AllowedEntraUserIds `
-TemplateFile $template `
-TemplateParameterObject @{
environmentName = $environmentName
location = $effectiveLocation
sharedResourceGroupName = $sharedResourceGroupName
adminUsername = $adminUsername
adminPassword = $adminPassword
storageAccountName = $storageAccountName
managedInstanceFQDN = $managedInstanceFQDN
vmName = $vmName
TeamName = $TeamName
legacySQLName = $legacySQLName
}

Write-Host "[$SubscriptionId] Lab deployment completed successfully"
Write-Host "[$SubscriptionId] Lab Resource Group: $effectiveResourceGroup"
Write-Host "[$SubscriptionId] Shared Resource Group: $sharedResourceGroupName"
Write-Host "[$SubscriptionId] Result: $($result)"

# feed the effective resource group back to the console
@{"HackboxCredential" = @{ name = "ResourceGroupName" ; value = $effectiveResourceGroup; note = "The name of the resource group where lab resources are deployed" }}
@{"HackboxCredential" = @{ name = "Team VM Name" ; value = $vmName; note = "The name of the Team VM" }}
@{"HackboxCredential" = @{ name = "Team / Link Name" ; value = $TeamName; note = "Name of the Team (Prefix for DBs and to use as Link Name for SQLMI)" }}
Loading