суббота, 6 апреля 2013 г.

Автоматизация публикации виртуальных машин в UAG 2010.

Microsoft Forefront Unified Access Gateway (UAG) 2010 - очень интересное средство для публикации внутренних ресурсов для внешних (да и внутренних тоже) пользователей. Но вот есть в нем очень серьезный недостаток - нет никаких средств автоматизации процесса управления.

Точнее так, средства автоматизации есть, в виде COM-интерфесов, но они недокументированы и понять, как они работают и какие параметры принимают, мне так и не удалось. Информации на просторах Интернет об этом я также не нашел.
Дополнительные неудобства вызывает и консоль управления UAG, явно выполненная не в майкрософтовском стиле (по известным причинам).

Ну и что с этого, скажете вы? Тогда немного об истоках проблемы.
Есть вычислительная среда, в виде энного количества серверов, включенных в группу управления VMM. В VMM нарезаны облака, на облака делегировано управление для конечных пользователей (разработчиков). В этих облаках разработчики создают виртуальные машины для своих нужд. Виртуальные машины необходимо как-то публиковать для доступа как снаружи так изнутри. На помощь нам приходит UAG - мы публикуем RDP виртуальных машин. Казалось бы, что еще нужно?
Но тут начинаются проблемы. Поскольку среда динамичная и относительно большая, то поддерживать такое решение (с ручной публикацией) становится практически невозможным:
- виртуальные машины постоянно создаются и удаляются (а это нужно актуализировать информацию).
- необходимо постоянно добавлять/удалять пользователей для опубликованных ярлыков UAG.

Вот здесь то мы и встречаемся с неудобной консолью управления UAG и отсутствием автоматизации.

Поэтому была поставлена задача как-то облегчить страдания группы поддержки и дать возможность конечным пользователям самим публиковать свои виртуальные машины, добавлять/удалять пользователей для ярлыков и держать это все в актуальном состоянии.

Дать доступ к консоли UAG конечным пользователям - не вариант.

После недолгих изучений, было обнаружено, что UAG хранит свою конфигурацию в файле UAG.egf, что находится в common\conf\ в папке установки. Файл текстовый, имеет некоторую иерархическую структуру. В качестве узлов иерархии выступают объекты, такие как, trunk, приложения, политики и т.д.

Теперь нужно было как-то разобрать эту информацию.
Полностью описывать структуры файла не буду, да и таких знаний у меня нет. Меня больше интересовали именно приложения RDP.
Структура файла:
{
     настройки самого UAG (версия схемы, алгоритмы шифрования и т.д.)
     {
      Репозитории аутентификации
       {
       }
      ...
     }
     {
        trunk http (80) и его настройки
      }
      {
         trunk https и его настройки
         {
                опубликованные приложения  <<< то что нам нужно
         }
      }
...........
}

Приложение формируется из набора атрибутов со значениями.
Формат атрибута следующий:

m_<тип><наименование атрибута>_<ID-приложения>[_<податрибуты и коды>]=[<значение>]

В "[]" заключены необязательные данные.
К типам относятся b (boolean), n (numeric), cs (скорее всего case-sensitive string) и т.д. Хотя конечно это не нужная для нас информация, мы ее никак использовать не будем.
Наименование атрибута - думаю понятно что это. Примеры: AppName, AppID, AppAuthList.
У приложения есть целочисленный уникальный идентификатор (ID-приложения). Уникальный скорее всего в пределах транка (Trunk).
Могут быть некоторые податрибуты. Например, для AuthList - это Repository, UserName и т.д.
Если например мы публикуем ярлык для нескольких пользователей, то податрибуты для разных пользователей еще будут различаться по целочисленному уникальному идентификатору.
После знака "равно" идет значение, хотя оно может отсутствовать.

Список атрибутов для ярлыка RDP можно посмотреть здесь - http://pastebin.com/5sz0EV9z
Список атрибутов для конкретного приложения можно получить с помощью вот этого скрипта (Powershell) - http://pastebin.com/WqHP0fx9. На входе путь к файлу UAG и имя приложения, которое отображается в консоли. На выходе список атрибутов со значениями.

В принципе достаточно повторить все эти атрибуты, добавить в секцию приложений конкретного транка, сохранить и активировать конфигурацию. В итоге нужный ярлык с приложением должен появиться на UAG-портале.
Но не все так просто. У приложения есть идентификатор вот в таком виде:
m_csAppId_24=7F81AB0527A443BEBA2018216CBCD110.
Когда изначально разбирался в структуре файла, я думал что это некий хэш MD5. Тем более очень похож, 128 бит. Пробовал его прикладывать к имени приложения, но результат отрицательный. К чему бы еще приложить - не нашел. Сейчас пишу эту статью и в голову пришла мысль, что это обычный GUID, без знаков "-" и фигурных скобок. Возможно в ближайшее время эта информация подтвердится.

Дополнительно нужно было убедиться, что в файле не меняются другие атрибуты или если меняются то какие и где.
Для этого написал небольшой скрипт для получения различий между двумя файлами (сначала поискал готовые утилиты, но они меня не удовлетворили, а тратить время на дальнейший поиск и изучение еще и этого вопроса вовсе не хотелось). Скрипт можно взять здесь - http://pastebin.com/3w5hp4Wz. На входе пути файлов Source и Dest.
Как выяснилось изменяются еще ряд атрибутов, такие как количество приложений в транке.

Все это я учел и начал вручную публиковать RDP-ярлык.
То есть, я беру и повторяю список атрибутов для приложения "Personal RDP", изменяю некоторые атрибуты (имена, ID и т.д.), изменяю дополнительные атрибуты и активирую конфигурацию. Мое приложение на портале не появляется и конфигурация откатывается на изначальную. Ошибок почему-то в мониторе активации нет.

Тогда я иду другим способом. Создаю некоторое приложение через консоль UAG. Далее изменяю нужные мне атрибуты уже через текстовый файл: имя, отображаемое имя, внутренний IP-адрес, список авторизации и т.д.. Применяю конфигурацию. Работает!
Ну собственно таким способом и решил действовать.

В результате родился следующий скрипт (Powershell): http://pastebin.com/jW9S0qaC
Список функций:

  • Set-UAGConfig - устанавливает глобальные переменные с расположением файла-egf, расположением каталога для бэкапов файлов-egf и путь к активатору (об этом немного позже). По умолчанию, в качестве $SourceFile берется UAG.egf из каталога установки UAG. $BackupDir - c:\EGFBackups (если не существует, то происходит попытка создания каталога). $UagActivator - по умолчанию C:\Scripts\UAGActivate.exe.
  • Backup-UAGConfig - копирует egf-файл из $SourceFile в $BackupDir с добавлением временной метки (для уникальности)
  • Disable-UAGApp - отключает опубликованное приложением, переименовывает в Unused<ID> и очищает некоторые атрибуты. Например, если вы хотите удалить приложение, то вызываете эту функцию.
  • Enable-UAGApp - включает приложение. Эта функция находит доступное отключенное приложением (с именем Unused<ID>), включает его и присваивает ему атрибуты. Какие? Имя приложения, имя приложения на портале, в ShortDescription попадает ID облака (это конкретно моя реализация), в LongDescription попадает GUID виртуальной машины, IP-адрес внутреннего сервера (ВМ). Большинство этих атрибутов устанавливается конкретно под мои задачи. Вам они скорее всего не подойдут. В этом случае необходимо изменить скрипт.
  • Set-UAGApp - обновляет атрибуты для уже опубликованного приложения. В моем случае это имя приложения, имя приложения на портале, ProjectID -> ShortDescription, VMID -> LongDescription и IP-адрес.
  • Get-UAGAuth - возвращает список авторизации для опубликованного приложения (выходной формат описан далее).
  • Set-UAGAuth - устанавливает список авторизации для опубликованного приложения.
  • Get-UAGAppList - возвращает список приложений, опубликованный для конкретного облака по ProjectID (это конкретно под мою задачу).
  • Get-UAGApp - возвращает приложение по VMID или по имени на портале (VMID берется из LongDescription).
  • Activate-UAGConfig - активирует конфигурацию с помощью файла из переменной $UAGActivator.

Как этим пользоваться?

Предварительный этап:
1) Необходимо через UAG консоль создать пул приложений RDP, с произвольными именами (отображение на портале нужно включить, список авторизации пустой), сохраняете конфигурацию (опционально можно ее применить).
2) Далее вы импортируете функции в консоль Powershell:
Import-Module "c:\Scripts\UAGFunctions.ps1"
3) делаете резервную копию файла конфигурации
Backup-UAGConfig
4) Отключаете созданные приложения
Disable-UAGApp -AppName "Имя приложения на портале"
Внимание!!! Обратите внимание, что в моей реализации я работаю с приложениями по имени, отображаемому на портале или по VMID, который я сохраняю в атрибуте LongDescription.
5) Активируете конфигурацию командой
Activate-UAGConfig

Далее 1 шаг - включаем свободное приложение (Unused):

  1. $ErrorActionPreference = "Stop"
  2. Get-PSSession | Remove-PSSession
  3. #Пароль для учетной записи, которая подключается удаленно к UAG
  4. $SecureString = ConvertTo-SecureString -String "\`d.T.~Vb/{41BAA34C-B0E6-41D0-8F2B-CB3901AAF6A8}\`d.T.~Vb/" -AsPlainText-Force
  5. $Creds = New-Object system.Management.Automation.PSCredential("domain\os2uag_connector", $SecureString)
  6. $SessionOpt = New-PSSessionOption -OperationTimeout (30*60*1000)
  7. #Подключаемся к UAG через PSRemoting
  8. $Session = New-PSSession -ComputerName "server-uag01" -Credential $Creds -SessionOption $SessionOpt
  9. #Импортируем модуль с функциями
  10. Invoke-Command -Session $Session -ScriptBlock {Import-Module "c:\Scripts\UAGFunctions.ps1"}
  11. #Устанавливаем глобальные переменные $BackupDir и UAGActivator
  12. Invoke-Command -Session $Session -ScriptBlock {Set-UAGConfig -BackupDir "c:\EGFBackups" -UAGActivatorPath"c:\Scripts\UAGActivate.exe" }
  13. #Бэкапим конфиг
  14. Invoke-Command -Session $Session -ScriptBlock {Backup-UAGConfig}
  15. $ScriptBlock = {
  16.         #Заполняем входную информацию для публикации
  17.         $VMID = "\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.VM ID\`d.T.~Ed/"
  18.         $AppName = "\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.VM Name\`d.T.~Ed/"
  19.         $ProjectID = [int]("\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.Cloud\`d.T.~Ed/".Split("_")[0].Substring(1))
  20.         $IPAddress = "\`d.T.~Ed/{90F45712-817A-4429-A5F0-668F26467CFD}.{08BCA15A-22A5-4770-81E7-1D41E21D1CDA}\`d.T.~Ed/"
  21.        
  22.         #Смотрим опубликовано ли уже приложение по имени
  23.         $App = Get-UAGApp -AppName $AppName
  24.         if ($App){
  25.                 if ($App.VMID -ne ($VMID.ToString().ToUpper().Replace("-",""))){
  26.                         #Если приложение (ВМ) с таким именем опубликовано и VMID различаются, то скорее всего старую ВМ удалили и создали с тем же именем
  27.                         #в этом случае отключаем исходное
  28.                         Disable-UAGApp -AppName $AppName
  29.                         #включаем ярлык, заполняем атрибутами
  30.                         Enable-UAGApp -AppName $AppName -AppGUID $VMID -ProjectID $ProjectID -IpAddr $IPAddress
  31.                 }
  32.                 else{
  33.                         #обновляем информацию о приложении
  34.                         Set-UAGApp -AppName $AppName -IpAddr $IPAddress -ProjectID $ProjectID
  35.                 }
  36.         }
  37.         else{
  38.                 $App = Get-UAGApp -VMID $VMID
  39.                 if (-not $App){
  40.                         #Включаем Unused приложение
  41.                         Enable-UAGApp -AppName $AppName -AppGUID $VMID -ProjectID $ProjectID -IpAddr $IPAddress
  42.                 }
  43.                 else {
  44.                         #обновляем информацию о приложении
  45.                         Set-UAGApp -AppName ($App.AppPortalName) -AppGUID $VMID -IpAddr $IPAddress -NewAppName $AppName-ProjectID $ProjectID
  46.                 }
  47.         }
  48. }
  49. #выполняем скрипт-блок, сформированный ранее
  50. Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
  51. $Session | Remove-PSSession


Шаг 2 - настраиваем авторизацию:

  1. $ErrorActionPreference = "Stop"
  2. $SecureString = ConvertTo-SecureString -String "\`d.T.~Vb/{41BAA34C-B0E6-41D0-8F2B-CB3901AAF6A8}\`d.T.~Vb/" -AsPlainText-Force
  3. $Creds = New-Object system.Management.Automation.PSCredential("domain\os2uag_connector", $SecureString)
  4. $SessionOpt = New-PSSessionOption -OperationTimeout (30*60*1000)
  5. $Session = New-PSSession -ComputerName "server-uag01" -Credential $Creds -SessionOption $SessionOpt
  6. Invoke-Command -Session $Session -ScriptBlock {Import-Module "c:\Scripts\UAGFunctions.ps1"}
  7. Invoke-Command -Session $Session -ScriptBlock {Set-UAGConfig -BackupDir "c:\EGFBackups" -UAGActivatorPath"c:\Scripts\UAGActivate.exe" }
  8. $ScriptBlock = {
  9.         #Имя приложения, отображаемое на портале
  10.         $AppName = "\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.VM Name\`d.T.~Ed/"
  11.         #DN пользователя в AD (я использую Active Directory Repository, для других репозиториев эти атрибуты могут отличаться)
  12.         $UserDN = "\`d.T.~Ed/{D6BCE08A-3CA4-4480-9482-4635B5083573}.Distinguished Name\`d.T.~Ed/"
  13.         $UserCanArr = @($UserDN.split(",") | where {$_ -notlike "dc=*"} | foreach {$_.Substring(3)})
  14.         $UserCanArr = @(for($i=($UserCanArr.Count-1);$i -ge 0$i--) { $UserCanArr[$i] })
  15.         #User Canonical String без домена (это тоже особенность Active Directory Repository в UAG)
  16.         $UserCanStr = [string]::Join('\', $UserCanArr)
  17.         #Получаю список авторизации для приложения
  18.         $authlist = Get-UAGAuth -AppName $AppName
  19.         #Создаю новый объект
  20.         $userObj = New-Object System.Object
  21.         #DN
  22.         $userObj | Add-Member -Name ID -MemberType NoteProperty -Value $UserDN
  23.         #Тип - 1 (пользователь)
  24.         $userObj | Add-Member -Name IsUser -MemberType NoteProperty -Value "1"
  25.         #Это имя репозитория (не имя домена, а именно репозитория аутентификации)
  26.         $userObj | Add-Member -Name Repository -MemberType NoteProperty -Value "domain.com"
  27.         #User Canonical name
  28.         $userObj | Add-Member -Name UserName -MemberType NoteProperty -Value $UserCanStr
  29.         #Разрешить - 1 (для блокирования видимо 0)
  30.         $userObj | Add-Member -Name Allow -MemberType NoteProperty -Value "1"
  31.         #добавляю к исходному списку (hash-таблица, в которой в качестве ключа используется DN) нового пользователя
  32.         $authlist[$userObj.ID] = $userObj
  33.         #Устанавливаю список авторизации для приложения
  34.         Set-UAGAuth -AppName $AppName -UserArray $authlist
  35. }
  36. #Выполняю скрипт-блок, подготовленный ранее
  37. Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
  38. #Здесь можно активировать, но в моем случае активация конфигурации происходит чуть позже
  39. #Invoke-Command -Session $Session -ScriptBlock { Activate-UAGConfig }
  40. $Session | Remove-PSSession


Теперь завершающий этап - активация:

  1. $ErrorActionPreference = "Stop"
  2. $SecureString = ConvertTo-SecureString -String "\`d.T.~Vb/{41BAA34C-B0E6-41D0-8F2B-CB3901AAF6A8}\`d.T.~Vb/" -AsPlainText-Force
  3. $Creds = New-Object system.Management.Automation.PSCredential("domain\os2uag_connector", $SecureString)
  4. $SessionOpt = New-PSSessionOption -OperationTimeout (30*60*1000)
  5. $Session = New-PSSession -ComputerName "server-uag01" -Credential $Creds -SessionOption $SessionOpt
  6. Invoke-Command -Session $Session -ScriptBlock {Import-Module "c:\Scripts\UAGFunctions.ps1"}
  7. Invoke-Command -Session $Session -ScriptBlock {Set-UAGConfig -BackupDir "c:\EGFBackups" -UAGActivatorPath"c:\Scripts\UAGActivate.exe" }
  8. Invoke-Command -Session $Session -ScriptBlock { Activate-UAGConfig }
  9. $Session | Remove-PSSession


Второй и последний этап можно было объединить, но в моем случае добавляется несколько пользователей из предлагаемого списка и только завершающим этапом активируется конфигурация.

Что за UAGActivate.exe?
Это скрипт Autoit, скомпилированный в .exe файл. Он запускает ConfigMgrUtil.exe для активации конфигурации UAG и посылает ему нажатия клавиш. Почему-то ConfigMgrUtil.exe отказался, в моем случае, принимать команды через поток StdIn, пришлось извращаться и делать через autoit.

Скрипт autoit:

Run("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe -a")
WinWait("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe")
ControlSend("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe", "", "", "{ENTER}")
ControlSend("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe", "", "", "{ENTER}")
ControlSend("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe", "", "", "{ENTER}")

Сам autoit здесь - http://www.autoitscript.com/site/autoit/
Скомпилированный выше скрипт здесь - http://www.filehosting.org/file/details/428697/UAGActivate.exe
SHA-1: b97f3151c3016f5eba54b460c43545d1810e5d21
Этот файл нужно сохранить под оригинальным именем (UAGActivate.exe) в папке c:\Scripts. Либо при выполнении скрипта/модуля с UAG-функциями установить путь к активатору.

У меня реализована схема публикации через Service Manager Portal (здесь пользователи создают заявки), далее отрабатывает System Center 2012 Orchestrator (бывший Opalis).
Runbook в Orchestrator выглядит вот так:
Скрипт с функциями был написан для схемы UAG версии 2010 SP1, но без изменений работает на SP3. Тестировался на протяжении месяца, без каких-либо проблем.

2 комментария:

Анонимный комментирует...
Этот комментарий был удален администратором блога.
Анонимный комментирует...
Этот комментарий был удален администратором блога.