Имеется сервер на базе ОС Windows Server 2012 R2 с несколькими экземплярами СУБД Firebird 2.5, выполняющимися в режиме службы Windows от имени разных учётных записей Group Managed Service Account (gMSA). Для некоторых баз данных (БД), выполняемых в экземпляре Firebird, необходимо настроить периодическое резервное копирование. При этом к разным БД выдвигаются разные требования по срокам хранения количества последних полных резервных копий. В этой заметке мы рассмотрим вариант решения поставленной задачи с помощью PowerShell и штатных инструментов Firebird.
Предварительные условия
Учитывая то, что для решения поставленной задачи нам желательно использовать штатные средства, поставляемые в составе СУБД Firebird, мы встанем перед выбором между использованием утилиты nbackup и gbak. Каждый из этих инструментов имеет свои особенности, но, учитывая ограничения nbackup, выбор остановлен на утилите gbak. Получить онлайн-справку об этой утилите можно, например, по ссылке: Firebird Manual - GBAK - Firebird Backup and Restore.
Описанное далее решение в виде PowerShell-скрипта отталкивается от ряда имеющихся в исходной среде дополнительных условий, связанных с усилением мер безопасности по работе с экземплярами Firebird, а именно:
- Каждый экземпляр Firebird выполняется от имени отдельной учётной записи gMSA и имеет выделенный дисковый том с файловой системой NTFS и с определённой структурой каталогов.
- Каждый экземпляр Firebird настроен в режиме обязательного использования алиасов БД с невозможностью подключаться к БД с указанием абсолютного пути к файлу БД.
- Операции резервного копирования не должны выполняться от имени администратора всего экземпляра Firebird, то есть не должна использоваться учётная запись системного супер-пользователя SYSDBA.
- Скрипт резервного копирования или его вспомогательный конфигурационный файл не должны содержать в себе никаких сведений об учётных записях (имя пользователя и пароль) для доступа к БД.
Третий и четвёртый пункты будут решены за счёт того, что выполнять скрипт резервного копирования мы будем от имени той же учётной записи gMSA, от имени которой выполняется сам экземпляр Firebird. Помимо этого, вызываемое в скрипте подключение к БД Firebird с помощью утилиты gbak также будет выполняться в контексте этой же учётной записи gMSA. А чтобы это было возможно, мы предварительно включим в экземпляре Firebird поддержку Windows-аутентификации и выдадим учётной записи gMSA роль RDB$ADMIN в каждой интересующей нас БД.
Структура каталогов Firebird
Используемые в скрипте резервного копирования переменные, определяющие каталоги размещения файлов БД и резервных копий отталкиваются от имеющейся в нашем случае файловой инфраструктуры, хотя и могут быть при необходимости заменены на другие.
В рассматриваемом примере экземпляр Firebird расположен выделенном дисковом томе D:\.
Для понимания опишем структуру подкаталогов, так как их размещение играет свою определённую роль.
- В подкаталоге \Bin размещены каталоги и исполняемые файлы самого экземпляра Firebird. Здесь, в частности, расположены конфигурационные файлы, определяющие работу экземпляра, такие как firebird.conf и aliases.conf.
- В подкаталоге \Data размещены файлы БД (*.fdb), резервное копирование которых мы и будем настраивать.
- В подкаталоге \Tools размещён PowerShell-скрипт резервного копирования (FBBases-Backup.ps1) и его вспомогательный конфигурационный файл (FBBases-Backup.csv), в котором описаны параметры интересующих нас БД.
- Подкаталог \Backup является символической ссылкой на сетевой каталог, расположенный на отельном файловом сервере. В этот каталог будут сохраняться резервные копии БД Firebird c логами операций резервного копирования. Сделана эта ссылка для упрощения работы с резервными копиями из утилиты gbak, чтобы исключить использование прямых UNC-путей. Разумеется, на указанный сетевой каталог должны быть выданы полные права на уровне SMB и NTFS для учётной записи gMSA, от имени которой будет выполняться скрипт резервного копирования.
Скрипт резервного копирования FBBases-Backup.ps1
Блок базовых переменных скрипта подразумевает использование вышеописанной структуры каталогов. В случае необходимости, значения переменных могут быть изменены.
# Блок базовых переменных
# $gbakBin - Путь к исполняемому файлу gbak.exe
# $FBDBDir - Путь к каталогу с файлами БД, указанными в FBBases-Backup.csv
# $FBBcpDir - Путь к каталогу сохранения резервных копий БД и логов *
# * В этом каталоге для каждой БД должен быть создан подкаталог с именем БД.
# * На каждый подкаталог должны быть назначены права для возможности создания/удаления файлов
# $FBDBList - Путь конфигурационному файлу FBBases-Backup.csv, в котором описаны параметры резервного копирования БД
# $FBLogin / $FBPwd - Учётные данные администратора всех БД (можно не использовать в случае Windows-аутентификации)
# $useDBAliases - Использовать алиасы для подключения к БД (TCP-подключение)*
# * Алиасы должны быть указаны в FBBases-Backup.csv
#
$gbakBin = $PSScriptRoot + "\..\Bin\bin\gbak.exe"
$FBDBDir = $PSScriptRoot + "\..\Data\"
$FBBcpDir = $PSScriptRoot + "\..\Backup\"
$FBDBList = $PSScriptRoot + "\FBBases-Backup.csv"
#$FBLogin = 'sysdba'
#$FBPwd = 'P@zW0r8D'
[bool]$useDBAliases = $true
#
# Блок переменных для уведомлений по электронной почте в случае проблем
# $gvEmailFrom – Email адрес отправителя
# $gvEmailTo – Email адрес получателя
# $gvSMTPServer – FQDN имя почтового сервера
#
$gvEmailFrom = "KOM-DB3 Firebird Backup Script <KOM-DB3@holding.com>"
$gvEmailTo = "Firebird-Monitoring@holding.com"
$gvSMTPServer = "KOM-MAIL.holding.com"
#
# Блок отсылки уведомляющего письма об ошибках выполнения скрипта
#
Trap {
If ($Error){
$vEmailBody = "Firebird DB Backup Error on server " + $env:computername + `
"`n`nError : " + $Error
MailAlert ($vEmailBody)
}
Break
}
#
# Функция отсылки уведомления
#
Function MailAlert ($mBody) {
$vEmailSubj = "Firebird DB Backup Problem"
$vSMTP = New-Object Net.Mail.SMTPClient($gvSMTPServer)
$vSMTP.Send($gvEmailFrom, $gvEmailTo, $vEmailSubj, $mBody)
}
#
# Функция задач резервного копирования
#
Function ProcessDB ($fbDBFile,$fbDBAlias,$fbDBCopies) {
$FBBase = $FBDBDir + $fbDBFile
$FBBaseNAME = [IO.Path]::GetFileNameWithoutExtension($FBBase)
$FBBcpSubDir = $FBBcpDir + $FBBaseNAME
Write-Host 'Processing database: ' $FBBase -ForegroundColor Cyan
Write-Host 'Number of stored copies: ' $fbDBCopies
# Проверка наличия файла БД
#
If(![System.IO.File]::Exists($FBBase)) {
MailAlert ("Firebird DB file with path " + $FBBase + " doesn't exist! Check configuration file : " + $FBDBList)
Break
}
#
# Проверка наличия подкаталога для резервных копий БД
#
If(!(Test-Path $FBBcpSubDir)) {
MailAlert ("Backup directory with path " + $FBBcpSubDir + " doesn't exist! Please create a directory.")
Break
}
#
# Блок выполнения резервного копирования
#
$FBPostfix = (Get-Date).ToString("yyyy-MM-dd_HH-mm-ss")
$FBBaseFBK = $FBBcpSubDir +"\"+ $FBBaseNAME + "_" + $FBPostfix + ".FBK"
$FBBaseLOG = $FBBcpSubDir +"\"+ $FBBaseNAME + "_" + $FBPostfix + ".LOG"
Try {
Write-Host 'Backing up ...'
If ($useDBAliases) {
#& $gbakBin -b $fbDBAlias $FBBaseFBK -v -y $FBBaseLOG -user $FBLogin -pass $FBPwd
& $gbakBin -b $fbDBAlias $FBBaseFBK -v -y $FBBaseLOG -trusted -role 'RDB$ADMIN'
}
Else
{
#& $gbakBin -b $FBBase $FBBaseFBK -v -y $FBBaseLOG -user $FBLogin -pass $FBPwd
& $gbakBin -b $FBBase $FBBaseFBK -v -y $FBBaseLOG -trusted -role 'RDB$ADMIN'
}
}
Catch { MailAlert ("Firebird DB " + $FBBase + " backup exception: " + $_) }
#
# Блок удаления устаревших резервных копий
#
Write-Host 'Clean up old backups ...'
$FileMask = $FBBcpSubDir +"\"+ $FBBaseNAME + "*.FBK"
Get-ChildItem $FileMask -Recurse | Where { -not $_.PsIsContainer } |
Sort CreationTime -Descending |
Select -Skip $fbDBCopies | Remove-Item -Force
#
# Блок удаления устаревших лог-файлов, не имеющих одноимённых файлов резервных копий
#
Write-Host 'Clean up old log files ...'
$LogMask = $FBBcpSubDir +"\"+ $FBBaseNAME + "*.LOG"
$LogFiles = Get-ChildItem $LogMask -Recurse | Where { -not $_.PsIsContainer }
Foreach ($LogFile in $LogFiles) {
$BcpFile = $LogFile.FullName -iReplace ".log",".FBK"
If(![System.IO.File]::Exists($BcpFile)) { Remove-Item $LogFile -Force }
}
}
#
# Обработка входных данных файла
#
$ConvFile = $FBDBList + '_unicode'
Get-Content $FBDBList | Set-Content $ConvFile -Encoding Unicode
Import-CSV $ConvFile -Delimiter ';' | ForEach-Object {
ProcessDB $_.DBFile $_.DBAlias $_.Copies
}
Remove-Item $ConvFile
#
Write-Host 'The script is complete.'
Текущий вариант скрипта предполагает использование Windows-аутентификации при подключении к каждой БД, перечисленной в конфигурационном файле FBBases-Backup.csv. Если же всё-таки по какой-то причине вам потребуется использовать менее безопасный сценарий, например, с использованием учётной записи SYSDBA для подключения ко всем БД, то раскомментируйте и задайте переменные $FBLogin и $FBPwd, а также измените вызов $gbakBin, закомментировав строки с параметром "-trusted" и раскомментировав строки с параметром "-user".
Конфигурационный файл FBBases-Backup.csv
Дополнительный конфигурационный файл FBBases-Backup.csv считывается при запуске выше приведённого скрипта FBBases-Backup.ps1. Первая строка файла содержит заголовки параметров (её менять не нужно). Вторая и последующие строки содержат сведения о БД, для которых будет выполняться резервное копирование.
DBFile;DBAlias;Copies
DATABASE1.FDB;localhost/801:db1;3
MYHUGEDB2.FDB;localhost/801:dbR;4
Первый столбец (DBFile) – реальное имя файла БД, расположенного в подкаталоге \Data. Второй столбец (DBAlias) – адрес подключения к БД в случае использования алиасов. Третий столбец (Copies) – количество резервных копий, которые должны сохраняться. Старые резервные копии (более старшие по времени) удаляются.
Подготовка экземпляра Firebird
Для возможности подключения к БД с использованием Windows-аутентификации, нам потребуется предварительно изменить конфигурацию экземпляра Firebird.
В конфигурационном файле firebird.conf значение параметра Authentication изменим со значения по умолчанию "native" на значение "mixed". Приведу соответствующий фрагмент конфигурационного файла:
...
# ----------------------------
# Which authentication method(s) should be used.
# "native" means use of only traditional interbase/firebird
# authentication with security database.
# "trusted" (Windows Only) makes use of window trusted authentication,
# and in some aspects this is the most secure way to authenticate.
# "mixed" means both methods may be used.
#
# Type: string
#
#Authentication = native
Authentication = mixed
...
Для вступления изменений в силу перезапустим службу экземпляра Firebird.
Теперь базы данных, работающие в нашем экземпляре Firebird, могут аутентифицировать Windows-пользователей.
Назначим учётной записи gMSA, от имени которой работает служба Firebird (и от имени которой мы будем вызывать скрипт резервного копирования и подключаться из него к БД), роль RDB$ADMIN на уровне каждой БД, которая указана нами в конфигурационном файле FBBases-Backup.csv.
С помощью клиента isql подключимся к первой базе данных \Data\DATABASE1.FDB с алиасом "db1", используя TCP соединение и учётные данные действующего администратора БД, например, супер-пользователя SYSDBA:
isql localhost/801:db1 -user sysdba -password P@zW0r8D
Войдя в интерактивный режим SQL-сессии, заглянем в системную таблицу, которая есть в каждой БД - RDB$ROLES. Это таблица, в которой хранятся роли для управления доступом к объектам и операциям с БД. Посмотрим перечень доступных ролей SQL-запросом:
SELECT RDB$ROLE_NAME FROM RDB$ROLES;
Роль RDB$ADMIN является предопределённой системной ролью и предоставляет полный доступ к БД. Назначим учётной записи gMSA данную роль SQL-запросом:
GRANT RDB$ADMIN TO "KOM\s-S11$";
После этого убедимся в том, что пользователю успешно назначена роль RDB$ADMIN запросами вида:
SHOW ROLE RDB$ADMIN;
SHOW GRANTS RDB$ADMIN;
Завершим интерактивную SQL-сессию командой "EXIT;"
Аналогичным образом выдадим роль RDB$ADMIN учётной записи gMSA во всех БД, которые указаны в конфигурационном файле FBBases-Backup.csv.
Тестируем работу скрипта
Учитывая то, что в нашем случае для запуска скрипта резервного копирования используется учётная запись gMSA, пароль этой учётной записи нам неизвестен, так как он обслуживается в автоматическом режиме контроллерами домена Active Directory. Поэтому проверить работу скрипта от имени сервисной учётной записи стандартным способом "запуск от имени" у нас не получится. Воспользуемся для этой задачи утилитой PsExec. Если запуск этой утилиты заблокирован администратором домена, можем воспользоваться ранее описанным методом, чтобы обойти такое ограничение.
Открываем командную строку с правами Администратора и выполняем вызов утилиты PsExec с параметрами, которые позволят интерактивно запустить отдельный экземпляр cmd.exe от имени учётной записи gMSA. Обратите внимание на то, что в качестве пароля здесь мы указываем знак тильды "~":
PsExec64.exe -i -u KOM\s-S11$ -p ~ cmd.exe
При выполнении этой команды откроется отдельное интерактивное окно интерпретатора командной строки cmd.exe. C помощью команды whoami проверяем то, что мы действительно находимся в контексте учётной записи gMSA.
Сделаю маленькое отступление и обращу внимание на то, что пока работает приложение, запущенное через PsExec, не стоит закрывать родительское окно командной строки, из которого был вызван PsExec. Так как на время сессии PsExec в системе регистрируется специальная служба PSEXESVC, и если некорректно завершить работу PsExec, эта служба не будет удалена из системы. Служба PSEXESVC удаляется автоматически только при штатном завершении сессии PsExec.
Итак, находясь в окружении пользователя gMSA, мы можем протестировать работу скрипта резервного копирования. Но предварительно будет не лишним проверить подключение к БД с использованием Windows-аутентификации от имени этого пользователя. Для этого в сеансе командной строки учётной записи gMSA перейдём в каталог с исполняемыми файлами Firebird и выполним попытку подключения к БД с использованием утилиты isql с ключом "-trusted", который включает форсированную Windows-аутентификацию:
cd /d D:\FBInst1\Bin\bin
isql localhost/801:db1 -trusted
Успешно подключившись к БД, ещё раз проверим свои права на уровне этой БД, выполнив запрос:
SHOW GRANTS;
Получив листинг своих прав, убедимся в том, что в списке присутствует выполненное нами ранее назначение роли RDB$ADMIN.
Завершим SQL-сессию командой "EXIT;"
Таким образом мы проверили возможность подключения к БД с использованием Windows-аутентификации и убедились в наличии полных прав к БД.
Теперь здесь же, в командной строке учётной записи gMSA, попробуем вызвать скрипт резервного копирования командой типа:
powershell.exe -NoProfile -command "D:\FBInst1\Tools\FBBases-Backup.ps1"
Если ранее мы все настройки произвели корректно, то скрипт должен отработать без ошибок, успешно создав полную резервную копию для каждой БД, описанной в файле FBBases-Backup.csv.
Если по какой-то причине скрипт отрабатывает, но резервные копии не создаются, то для проверки стоит попробовать напрямую вызвать утилиту gbak командой вида:
cd /d D:\FBInst1\Bin\bin
gbak.exe -b localhost/801:db1 D:\FBInst1\Backup\DATABASE1\DATABASE1_TEST_BACKUP.FBK -v
По окончании выполненных проверок закрываем сессию интерпретатора cmd.exe, запущенную от имени gMSA, выполнив в консоли команду exit
На этом тестирование работы скрипта резервного копирования можем считать выполненным.
Создаём задачу планировщика Windows
Последнее, что нам остаётся сделать, это настроить задание в планировщике Windows (Task Scheduler), которое будет отвечать за автоматический запуск скрипта резервного копирования по интересующему нас расписанию. В нашем случае запуск скрипта должен выполняться от имени учётной записи gMSA, однако графическая оболочка планировщика Windows не умеет работать с учётными записями подобного типа. Поэтому для настройки задания планировщика Windows нам снова придётся воспользоваться возможностями PowerShell.
Запустим оболочку PowerShell с правами Администратора и создадим задание планировщика Windows примерно следующим образом:
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -command `"D:\FBInst1\Tools\FBBases-Backup.ps1`""
$Trigger = New-ScheduledTaskTrigger -Daily -At 23:00
$SvcUser = New-ScheduledTaskPrincipal -UserID KOM\s-S11$ -LogonType Password
Register-ScheduledTask -TaskName "Firebird DBs (Instance1) Backup" -Action $Action -Trigger $Trigger -Principal $SvcUser
Откроем графическую консоль управления планировщиком Windows и проверим корректность созданного задания.
В случае необходимости изменения созданного задания с использованием учётной записи gMSA не нужно пытаться изменить здание через графическую консоль, так как это приведёт к запросам аутентификации для учётной записи gMSA и ошибкам сохранения задания. Изменить такое задание можно с помощью PowerShell. Например, чтобы изменить время запуска задания можем выполнить следующий код:
$Trigger = New-ScheduledTaskTrigger -Daily -At 23:05
Set-ScheduledTask -TaskName "Firebird DBs (Instance1) Backup" -Trigger $Trigger
На этом настройку резервного копирования баз данных Firebird средствами PowerShell с использованием управляемой учётной записи Group Managed Service Account можно считать выполненной.
Дополнительные источники информации:
Спасибо за полезную статью! Как раз будет повод перевести свои Interbase сервера на gMSA и бэкапы тоже переписать. Исторически сложилось, что всё это бэкапится с указанием пароля sysdba и очень здорово, что есть нормальные способы избавиться от такой уязвимости.
За пример использования gMSA в планировщике отдельные спасибо - я как увидел, что через графику нельзя, то и углубляться дальше не стал, думал это не поддерживается и вариантов нет. А оно вон как - через PoSh пожалуйста, доступно.