Skip to content

Commit a26e619

Browse files
Add-DbaAgDatabase, New-DbaAvailabilityGroup - auto-copy TDE certificate to replicas (#10237)
1 parent 3e26310 commit a26e619

4 files changed

Lines changed: 62 additions & 0 deletions

File tree

public/Add-DbaAgDatabase.ps1

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ function Add-DbaAgDatabase {
8383
When enabled, Restore-DbaDatabase uses the replica's default data and log directories instead of attempting to replicate the primary's folder structure.
8484
This is automatically set to true when the primary and replica servers run on different operating system platforms (e.g., Windows primary with Linux replica).
8585
86+
.PARAMETER MasterKeySecurePassword
87+
Password for creating or opening the database master key on secondary replicas when adding TDE-encrypted databases.
88+
When a database is protected by Transparent Data Encryption (TDE), the certificate used to protect the Database Encryption Key must exist on every secondary replica.
89+
Providing this parameter together with SharedPath allows the command to automatically copy the TDE certificate from the primary to each secondary replica.
90+
If the secondary already has a master key, this password is used to create one if it is missing.
91+
8692
.PARAMETER WhatIf
8793
Shows what would happen if the command were to run. No actions are actually performed.
8894
@@ -242,6 +248,9 @@ function Add-DbaAgDatabase {
242248
[switch]$SkipReuseSourceFolderStructure,
243249
[Parameter(ParameterSetName = 'NonPipeline')]
244250
[Parameter(ParameterSetName = 'Pipeline')]
251+
[Security.SecureString]$MasterKeySecurePassword,
252+
[Parameter(ParameterSetName = 'NonPipeline')]
253+
[Parameter(ParameterSetName = 'Pipeline')]
245254
[switch]$EnableException
246255
)
247256

@@ -365,6 +374,49 @@ function Add-DbaAgDatabase {
365374
}
366375
}
367376

377+
# For TDE-encrypted databases, the master certificate must exist on every secondary replica
378+
# before a backup can be restored or automatic seeding can succeed.
379+
if ($db.EncryptionEnabled -and $db.HasDatabaseEncryptionKey -and $db.DatabaseEncryptionKey.EncryptorType -eq "ServerCertificate") {
380+
$encryptorName = $db.DatabaseEncryptionKey.EncryptorName
381+
Write-Message -Level Verbose -Message "Database $($db.Name) is TDE-encrypted using certificate '$encryptorName'. Checking secondary replicas."
382+
if ($SharedPath) {
383+
$failure = $false
384+
foreach ($replicaName in $replicaServerSMO.Keys) {
385+
$replicaServer = $replicaServerSMO[$replicaName]
386+
$existingCert = Get-DbaDbCertificate -SqlInstance $replicaServer -Database master -Certificate $encryptorName
387+
if (-not $existingCert) {
388+
if ($Pscmdlet.ShouldProcess($replicaServer, "Copy TDE certificate '$encryptorName' from primary to replica $replicaName")) {
389+
try {
390+
Write-Message -Level Verbose -Message "TDE certificate '$encryptorName' not found on $replicaName. Copying from primary."
391+
$splatTdeCert = @{
392+
Source = $server
393+
Destination = $replicaServer
394+
Database = "master"
395+
Certificate = $encryptorName
396+
SharedPath = $SharedPath
397+
EnableException = $true
398+
}
399+
if ($MasterKeySecurePassword) {
400+
$splatTdeCert.MasterKeyPassword = $MasterKeySecurePassword
401+
}
402+
$null = Copy-DbaDbCertificate @splatTdeCert
403+
} catch {
404+
$failure = $true
405+
Stop-Function -Message "Failed to copy TDE certificate '$encryptorName' to replica $replicaName." -ErrorRecord $_ -Continue
406+
}
407+
}
408+
} else {
409+
Write-Message -Level Verbose -Message "TDE certificate '$encryptorName' already exists on replica $replicaName."
410+
}
411+
}
412+
if ($failure) {
413+
Stop-Function -Message "Failed to copy TDE certificate to all replicas for database $($db.Name)." -Continue
414+
}
415+
} else {
416+
Write-Message -Level Warning -Message "Database $($db.Name) is TDE-encrypted with certificate '$encryptorName', but no SharedPath was provided. The TDE certificate must exist on all secondary replicas before the database can be added. Use -SharedPath and optionally -MasterKeySecurePassword to enable automatic certificate copying."
417+
}
418+
}
419+
368420
$progress['Status'] = "Step 2/5: Running backup and restore if needed"
369421
Write-Message -Level Verbose -Message $progress['Status']
370422
Write-Progress @progress

public/New-DbaAvailabilityGroup.ps1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ function New-DbaAvailabilityGroup {
172172
Manual (default) uses backup/restore through shared storage, Automatic uses direct network streaming.
173173
Use Automatic for SQL Server 2016+ to simplify setup when network bandwidth is sufficient and shared storage is limited.
174174
175+
.PARAMETER MasterKeySecurePassword
176+
Password for creating the database master key on secondary replicas when adding TDE-encrypted databases.
177+
When databases protected by Transparent Data Encryption (TDE) are added to the availability group, the TDE certificate
178+
in the primary's master database must be copied to each secondary. Providing this parameter alongside SharedPath enables
179+
automatic certificate copying. If a secondary replica is missing a master key, this password is used to create one.
180+
175181
.PARAMETER Certificate
176182
Specifies the certificate name for endpoint authentication instead of Windows authentication.
177183
Both endpoints must have matching certificates with corresponding public/private key pairs.
@@ -395,6 +401,7 @@ function New-DbaAvailabilityGroup {
395401
[int]$Port = 1433,
396402
[switch]$Dhcp,
397403
[string]$ClusterConnectionOption,
404+
[Security.SecureString]$MasterKeySecurePassword,
398405
[switch]$EnableException
399406
)
400407
begin {
@@ -791,6 +798,7 @@ function New-DbaAvailabilityGroup {
791798
}
792799
if ($SeedingMode) { $addDatabaseParams['SeedingMode'] = $SeedingMode }
793800
if ($SharedPath) { $addDatabaseParams['SharedPath'] = $SharedPath }
801+
if ($MasterKeySecurePassword) { $addDatabaseParams['MasterKeySecurePassword'] = $MasterKeySecurePassword }
794802
try {
795803
$null = Add-DbaAgDatabase @addDatabaseParams
796804
} catch {

tests/Add-DbaAgDatabase.Tests.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Describe $CommandName -Tag UnitTests {
2424
"AdvancedBackupParams",
2525
"NoWait",
2626
"SkipReuseSourceFolderStructure",
27+
"MasterKeySecurePassword",
2728
"EnableException"
2829
)
2930
Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty

tests/New-DbaAvailabilityGroup.Tests.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Describe $CommandName -Tag UnitTests {
4545
"Port",
4646
"Dhcp",
4747
"ClusterConnectionOption",
48+
"MasterKeySecurePassword",
4849
"EnableException"
4950
)
5051
Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty

0 commit comments

Comments
 (0)