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
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$SharedResourceGroup = 'rg-shared'
$SqlAdminLogin = 'sqlmiadmin'
$FabricApi = 'https://api.fabric.microsoft.com/v1'
$TailspinToysBak = 'tailspintoys_before_launch.bak'
$TailspinToysFeedbackBak = 'tailspintoysfeedback_before_launch.bak'
# Shared MI admin password: derive at subscription scope so it matches the shared hook's value.
$sqlPassword = New-MhhStablePassword -Purpose 'sql-admin' -Length 24 -ResourceGroupName ''

# ─────────────────────────────────────────────
# Helpers
Expand All @@ -45,10 +48,9 @@ if (-not (Get-Module -ListAvailable -Name SqlServer)) {
}
Import-Module SqlServer -ErrorAction Stop

function Get-DbAccessToken {
$t = (Get-AzAccessToken -ResourceUrl 'https://database.windows.net/').Token
if ($t -is [System.Security.SecureString]) { return (ConvertFrom-SecureString $t -AsPlainText) }
return $t
function Update-MhhTokenQuiet {
# Refresh Azure credentials; Update-MhhToken's status object is shown only with -Verbose.
Update-MhhToken | Out-String | Write-Verbose
}

function Invoke-MiSql {
Expand All @@ -58,7 +60,9 @@ function Invoke-MiSql {
[string]$Query,
[int]$QueryTimeout = 0
)
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -AccessToken (Get-DbAccessToken) `
# SQL authentication with the MI admin login: the platform cannot set an Entra admin on the MI.
$cred = [pscredential]::new($SqlAdminLogin, (ConvertTo-SecureString $sqlPassword -AsPlainText -Force))
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Credential $cred `
-Query $Query -ConnectionTimeout 30 -QueryTimeout $QueryTimeout -ErrorAction Stop
}

Expand Down Expand Up @@ -102,7 +106,7 @@ $server = "$publicFqdn,3342"
# ─────────────────────────────────────────────
# 1. Per-attendee ARM resources (CSV storage) into the attendee's RG
# ─────────────────────────────────────────────
Update-MhhToken
Update-MhhTokenQuiet
$rgResult = Invoke-MhhDeploymentWithRegionFallback `
-PreferredLocations $PreferredLocation `
-ResourceGroupName $ResourceGroupName `
Expand All @@ -125,7 +129,7 @@ if ($LASTEXITCODE -ne 0) { throw "Failed to upload employee CSV." }
# ─────────────────────────────────────────────
# 2. Restore the two attendee databases in the shared SQL MI
# ─────────────────────────────────────────────
Update-MhhToken
Update-MhhTokenQuiet
$storageAccount = az storage account list -g $SharedResourceGroup --query "[0].name" -o tsv
$containerName = 'build'
$storageKey = az storage account keys list --resource-group $SharedResourceGroup --account-name $storageAccount --query "[0].value" -o tsv
Expand Down Expand Up @@ -161,7 +165,7 @@ foreach ($r in $restores) {
# ─────────────────────────────────────────────
# 3. Attendee login/user (db_owner) + product in each database
# ─────────────────────────────────────────────
Update-MhhToken
Update-MhhTokenQuiet
Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 -Query @"
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = N'$upn')
CREATE LOGIN [$upn] FROM EXTERNAL PROVIDER;
Expand Down Expand Up @@ -189,7 +193,7 @@ if ($productExists -ne 1) {
# ─────────────────────────────────────────────
# 4. Fabric workspace on the shared capacity + attendee as Member
# ─────────────────────────────────────────────
Update-MhhToken
Update-MhhTokenQuiet
$capacityName = az resource list -g $SharedResourceGroup --resource-type 'Microsoft.Fabric/capacities' --query "[0].name" -o tsv
$capacity = (Invoke-FabricApi -Method GET -Path 'capacities').value | Where-Object { $_.displayName -eq $capacityName } | Select-Object -First 1
if (-not $capacity) { throw "Fabric capacity '$capacityName' not visible via the Fabric API." }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ param administratorLogin string
@secure()
param administratorLoginPassword string

@description('Entra admin display name (label only).')
param aadAdminLogin string

@description('Entra admin object ID (sid). Set explicitly so ARM does not resolve the principal.')
param aadAdminSid string

@description('Entra admin tenant ID.')
param aadAdminTenantId string

@description('Tags to apply to all resources.')
param tags object = {}

Expand Down Expand Up @@ -55,6 +64,18 @@ resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' = {
}
}

// Entra admin enables Azure AD authentication on the instance (SQL auth stays enabled). Explicit sid so ARM does not have to resolve the principal.
resource sqlAadAdmin 'Microsoft.Sql/managedInstances/administrators@2023-08-01-preview' = {
parent: managedInstance
name: 'ActiveDirectory'
properties: {
administratorType: 'ActiveDirectory'
login: aadAdminLogin
sid: aadAdminSid
tenantId: aadAdminTenantId
}
}

output id string = managedInstance.id
output name string = managedInstance.name
output fqdn string = managedInstance.properties.fullyQualifiedDomainName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ param addressSpace array = [
@description('Tags to apply to all resources.')
param tags object = {}

@description('When true, the SQL MI subnet NSG and route table already exist (the MI has injected network intent policies). They are then referenced instead of redeployed, which avoids ConflictWithNetworkIntentPolicy on shared hook re-runs.')
param sqlMiNetworkingExists bool = false

var mergedTags = union(tags, {
environment: envName
})
Expand All @@ -28,7 +31,7 @@ var mergedTags = union(tags, {

// ManagedInstance subnet NSG. In the Terraform version the NSG was created empty
// and the 3342 rule was added by the SQL MI module; here it is inlined.
resource sqlMiNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
resource sqlMiNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = if (!sqlMiNetworkingExists) {
name: 'nsg-sqlhackmi'
location: location
tags: mergedTags
Expand All @@ -51,6 +54,11 @@ resource sqlMiNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
}
}

// Referenced (not redeployed) when the SQL MI has already injected its network intent policies.
resource sqlMiNsgExisting 'Microsoft.Network/networkSecurityGroups@2023-11-01' existing = {
name: 'nsg-sqlhackmi'
}

resource bastionNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
name: 'sqlhack-shared-bastion-nsg'
location: location
Expand Down Expand Up @@ -415,7 +423,7 @@ resource streamingNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
// ─────────────────────────────────────────────
// Route table for the ManagedInstance subnet
// ─────────────────────────────────────────────
resource sqlMiRouteTable 'Microsoft.Network/routeTables@2023-11-01' = {
resource sqlMiRouteTable 'Microsoft.Network/routeTables@2023-11-01' = if (!sqlMiNetworkingExists) {
name: 'rt-sqlhackmi'
location: location
tags: mergedTags
Expand All @@ -424,6 +432,10 @@ resource sqlMiRouteTable 'Microsoft.Network/routeTables@2023-11-01' = {
}
}

resource sqlMiRouteTableExisting 'Microsoft.Network/routeTables@2023-11-01' existing = {
name: 'rt-sqlhackmi'
}

// ─────────────────────────────────────────────
// Virtual network with inline subnets
// ─────────────────────────────────────────────
Expand Down Expand Up @@ -453,10 +465,10 @@ resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
]
defaultOutboundAccess: false
networkSecurityGroup: {
id: sqlMiNsg.id
id: sqlMiNetworkingExists ? sqlMiNsgExisting.id : sqlMiNsg.id
}
routeTable: {
id: sqlMiRouteTable.id
id: sqlMiNetworkingExists ? sqlMiRouteTableExisting.id : sqlMiRouteTable.id
}
delegations: [
{
Expand Down Expand Up @@ -565,4 +577,4 @@ output vnetId string = vnet.id
output managedInstanceSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, 'ManagedInstance')
output appServiceSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, 'snet-appservice')
output fabricSubnetName string = 'fabric_vnet'
output sqlMiNsgName string = sqlMiNsg.name
output sqlMiNsgName string = 'nsg-sqlhackmi'
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// resources and are created by deploy-lab.ps1 against the shared MI/capacity.
// The employee CSV blob is uploaded by deploy-lab.ps1 (not an ARM operation).

@description('Azure region.')
param location string
@description('Azure region. Defaults to the resource group location so it follows the region-fallback helper.')
param location string = resourceGroup().location

@description('Entra object ID of the attendee, granted data access to the CSV storage.')
param attendeeObjectId string
Expand Down
Loading