Передача учетных данных с использованием шаблона DSC и ARM и развертывание с помощью Visual Studio 2017 - PullRequest
0 голосов
/ 30 ноября 2018

Я впервые пытаюсь создать файл DSC (требуемая конфигурация состояния) с шаблоном ARM (Azure Resource Manager) для развертывания Windows Server 2016, и до сих пор все работало отлично, пока я не попытался передать имя пользователя/ пароль, чтобы я мог создать локальную учетную запись пользователя Windows.Кажется, я вообще не могу выполнить эту функцию (см. Сообщение об ошибке ниже).

Мой вопрос: как использовать шаблон ARM, чтобы извлечь пароль из хранилища ключей Azure и передать его вРасширение DSC powershell?

Вот мои текущие настройки:

azuredeploy.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "deployExecUsername": {
      "type": "string",
      "defaultValue": "DeployExec"
    },
    "deployExecPassword": {
      "type": "securestring"
    },
    "_artifactsLocation": {
      "type": "string",
      "metadata": {
        "description": "Auto-generated container in staging storage account to receive post-build staging folder upload"
      }
    },
    "_artifactsLocationSasToken": {
      "type": "securestring",
      "metadata": {
        "description": "Auto-generated token to access _artifactsLocation"
      }
    },
    "virtualMachineName": {
      "type": "string",
      "defaultValue": "web-app-server"
    }
  },
  "variables": {
    "CreateLocalUserArchiveFolder": "DSC",
    "CreateLocalUserArchiveFileName": "CreateLocalUser.zip"},
  "resources": [
    {
      "name": "[concat(parameters('virtualMachineName'), '/', 'Microsoft.Powershell.DSC')]",
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "location": "eastus2",
      "apiVersion": "2016-03-30",
      "dependsOn": [ ],
      "tags": {
        "displayName": "CreateLocalUser"
      },
      "properties": {
        "publisher": "Microsoft.Powershell",
        "type": "DSC",
        "typeHandlerVersion": "2.9",
        "autoUpgradeMinorVersion": true,
        "settings": {
          "configuration": {
            "url": "[concat(parameters('_artifactsLocation'), '/', variables('CreateLocalUserArchiveFolder'), '/', variables('CreateLocalUserArchiveFileName'))]",
            "script": "CreateLocalUser.ps1",
            "function": "Main"
          },
          "configurationArguments": {
            "nodeName": "[parameters('virtualMachineName')]"
          }
        },
        "protectedSettings": {
          "configurationArguments": {
            "deployExecCredential": {
              "UserName": "[parameters('deployExecUsername')]",
              "Password": "[parameters('deployExecPassword')]"
            }
          },
          "configurationUrlSasToken": "[parameters('_artifactsLocationSasToken')]"
        }
      }
    }],
  "outputs": {}
}

azuredeploy.parameters.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "deployExecPassword": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/<GUID>/resourceGroups/<Resource Group Name>/providers/Microsoft.KeyVault/vaults/<Resource Group Name>-key-vault"
        },
        "secretName": "web-app-server-deployexec-password"
      }
    }
  }
}

DSC /CreateLocalUser.ps1

Configuration Main
{
    Param ( 
        [string] $nodeName,
        [PSCredential]$deployExecCredential 
    )

    Import-DscResource -ModuleName PSDesiredStateConfiguration

    Node $nodeName
    {
        User DeployExec
        {
            Ensure = "Present"
            Description = "Deployment account for Web Deploy"
            UserName = $deployExecCredential.UserName
            Password = $deployExecCredential
            PasswordNeverExpires = $true
            PasswordChangeRequired = $false
            PasswordChangeNotAllowed = $true
        }
    }
}

Deploy-AzureResourceGroup.ps1 (по умолчанию из шаблона группы ресурсов Azure)

#Requires -Version 3.0

Param(
    [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation,
    [string] $ResourceGroupName = 'AzureResourceGroup2',
    [switch] $UploadArtifacts,
    [string] $StorageAccountName,
    [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts',
    [string] $TemplateFile = 'azuredeploy.json',
    [string] $TemplateParametersFile = 'azuredeploy.parameters.json',
    [string] $ArtifactStagingDirectory = '.',
    [string] $DSCSourceFolder = 'DSC',
    [switch] $ValidateOnly
)

try {
    [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0')
} catch { }

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 3

function Format-ValidationOutput {
    param ($ValidationOutput, [int] $Depth = 0)
    Set-StrictMode -Off
    return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @('  ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) })
}

$OptionalParameters = New-Object -TypeName Hashtable
$TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile))
$TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile))

if ($UploadArtifacts) {
    # Convert relative paths to absolute paths if needed
    $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory))
    $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder))

    # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present
    $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json
    if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) {
        $JsonParameters = $JsonParameters.parameters
    }
    $ArtifactsLocationName = '_artifactsLocation'
    $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken'
    $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
    $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore

    # Create DSC configuration archive
    if (Test-Path $DSCSourceFolder) {
        $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName})
        foreach ($DSCSourceFilePath in $DSCSourceFilePaths) {
            $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip'
            Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose
        }
    }

    # Create a storage account name if none was provided
    if ($StorageAccountName -eq '') {
        $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19)
    }

    $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName})

    # Create the storage account if it doesn't already exist
    if ($StorageAccount -eq $null) {
        $StorageResourceGroupName = 'ARM_Deploy_Staging'
        New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force
        $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation"
    }

    # Generate the value for artifacts location if it is not provided in the parameter file
    if ($OptionalParameters[$ArtifactsLocationName] -eq $null) {
        $OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName
    }

    # Copy files from the local storage staging location to the storage account container
    New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1

    $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName}
    foreach ($SourcePath in $ArtifactFilePaths) {
        Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) `
            -Container $StorageContainerName -Context $StorageAccount.Context -Force
    }

    # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file
    if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) {
        $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force `
            (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4))
    }
}

# Create or update the resource group using the specified template file and template parameters file
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force

if ($ValidateOnly) {
    $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
                                                                                  -TemplateFile $TemplateFile `
                                                                                  -TemplateParameterFile $TemplateParametersFile `
                                                                                  @OptionalParameters)
    if ($ErrorMessages) {
        Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.'
    }
    else {
        Write-Output '', 'Template is valid.'
    }
}
else {
    New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
                                       -ResourceGroupName $ResourceGroupName `
                                       -TemplateFile $TemplateFile `
                                       -TemplateParameterFile $TemplateParametersFile `
                                       @OptionalParameters `
                                       -Force -Verbose `
                                       -ErrorVariable ErrorMessages
    if ($ErrorMessages) {
        Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") })
    }
}

Обратите внимание, что мой исходный шаблон развертывает весь сервер, но я могувоспроизведите проблему, с которой я столкнулся, с помощью вышеуказанного шаблона и любой старой виртуальной машины Windows Server 2016.

Я запускаю шаблон в сообществе Visual Studio 2017:

enter image description here

enter image description here

Шаблон проверяется и запускается, но при его запуске появляется следующее сообщение об ошибке:

22:26:41 - New-AzureRmResourceGroupDeployment : 10:26:41 PM - VM has reported a failure when processing extension 
22:26:41 - 'Microsoft.Powershell.DSC'. Error message: "The DSC Extension received an incorrect input: Compilation errors occurred 
22:26:41 - while processing configuration 'Main'. Please review the errors reported in error stream and modify your configuration 
22:26:41 - code appropriately. System.InvalidOperationException error processing property 'Password' OF TYPE 'User': Converting 
22:26:41 - and storing encrypted passwords as plain text is not recommended. For more information on securing credentials in MOF 
22:26:41 - file, please refer to MSDN blog: http://go.microsoft.com/fwlink/?LinkId=393729
22:26:41 - At C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\DSCWork\CreateLocalUser.4\CreateLocalUser.ps1:12 char:3
22:26:41 - +   User Converting and storing encrypted passwords as plain text is not recommended. For more information on securing 
22:26:41 - credentials in MOF file, please refer to MSDN blog: http://go.microsoft.com/fwlink/?LinkId=393729 Cannot find path 
22:26:41 - 'HKLM:\SOFTWARE\Microsoft\PowerShell\3\DSC' because it does not exist. Cannot find path 
22:26:41 - 'HKLM:\SOFTWARE\Microsoft\PowerShell\3\DSC' because it does not exist.
22:26:41 - Another common error is to specify parameters of type PSCredential without an explicit type. Please be sure to use a 
22:26:41 - typed parameter in DSC Configuration, for example:
22:26:41 -     configuration Example {
22:26:41 -         param([PSCredential] $UserAccount)
22:26:41 -         ...
22:26:41 -     }.
22:26:41 - Please correct the input and retry executing the extension.".
22:26:41 - At F:\Users\Shad\Documents\Visual Studio 2017\Projects\AzureResourceGroup2\AzureResourceGroup2\bin\Debug\staging\AzureR
22:26:41 - esourceGroup2\Deploy-AzureResourceGroup.ps1:108 char:5
22:26:41 - +     New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $Templat ...
22:26:41 - +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22:26:41 -     + CategoryInfo          : NotSpecified: (:) [New-AzureRmResourceGroupDeployment], Exception
22:26:41 -     + FullyQualifiedErrorId : Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.NewAzureResourceGroupDep 
22:26:41 -    loymentCmdlet

ЧтоЯ пытался

Я уже пытался смотреть на этот вопрос:

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

Я также взглянул на этот вопрос:

и принятый ответ - просто используйте PsDSCAllowPlainTextPassword = $true.Хотя это выглядит не лучшим образом, я попытался добавить следующий файл данных конфигурации:

CreateLocalUser.psd1

#
# CreateLocalUser.psd1
#
@{
    AllNodes = @(
        @{
            NodeName = '*'
            PSDscAllowPlainTextPassword = $true
        }
    )
}

и изменить Deploy-AzureResourceGroup.ps1, чтобы передать эти настройки в DSCконфигурации, как показано ниже.

# Create DSC configuration archive
if (Test-Path $DSCSourceFolder) {
    $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName})
    foreach ($DSCSourceFilePath in $DSCSourceFilePaths) {
        $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip'
        $DSCConfigDataFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.psd1'
        Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -ConfigurationDataPath $DSCConfigDataFilePath -Force -Verbose
    }
}

Тем не менее я не получаю никаких изменений в сообщении об ошибке при этом.

Я пытался решить эту проблему с помощью документации по Azure.из.Ссылка в сообщении об ошибке совершенно бесполезна, поскольку нет примеров того, как использовать шифрование с шаблоном ARM.В большинстве примеров показаны запущенные сценарии Powershell, а не шаблон ARM.И нигде в документации нет ни одного примера того, как извлечь пароль из хранилища ключей и передать его в файл расширения DSC из шаблона ARM.Это вообще возможно?

Обратите внимание, что я был бы рад просто использовать Visual Studio для развертывания, если бы я мог просто заставить его работать.Но я работаю над этой проблемой в течение нескольких дней и, похоже, не могу найти ни одного работающего решения.Итак, я подумал, что я хотел бы спросить здесь, прежде чем бросить полотенце и просто использовать учетную запись администратора Windows для веб-развертывания.

ОБНОВЛЕНИЕ 2018-12-21

Я заметил при запускеКоманда развертывания через Visual Studio 2017, что журнал содержит сообщение об ошибке:

20:13:43 - Build started.
20:13:43 - Project "web-app-server.deployproj" (StageArtifacts target(s)):
20:13:43 - Project "web-app-server.deployproj" (ContentFilesProjectOutputGroup target(s)):
20:13:43 - Done building project "web-app-server.deployproj".
20:13:43 - Done building project "web-app-server.deployproj".
20:13:43 - Build succeeded.
20:13:43 - Launching PowerShell script with the following command:
20:13:43 - 'F:\Projects\thepath\web-app-server\bin\Debug\staging\web-app-server\Deploy-AzureResourceGroup.ps1' -StorageAccountName 'staged<xxxxxxxxxxxxxxxxx>' -ResourceGroupName 'web-app-server' -ResourceGroupLocation 'eastus2' -TemplateFile 'F:\Projects\thepath\web-app-server\bin\Debug\staging\web-app-server\web-app-server.json' -TemplateParametersFile 'F:\Projects\thepath\web-app-server\bin\Debug\staging\web-app-server\web-app-server.parameters.json' -ArtifactStagingDirectory '.' -DSCSourceFolder '.\DSC' -UploadArtifacts
20:13:43 - Deploying template using PowerShell script failed.
20:13:43 - Tell us about your experience at https://go.microsoft.com/fwlink/?LinkId=691202

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

Ответы [ 2 ]

0 голосов
/ 21 декабря 2018

К сожалению, что бы я ни пытался, это не работает с DSC.

Обходной путь

На данный момент я работаю над этой проблемой, используя расширение собственного скрипта, например:

    {
      "name": "[concat(parameters('virtualMachineName'), '/addWindowsAccounts')]",
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "apiVersion": "2018-06-01",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[concat('Microsoft.Compute/virtualMachines/', parameters('virtualMachineName'))]"
      ],
      "properties": {
        "publisher": "Microsoft.Compute",
        "type": "CustomScriptExtension",
        "typeHandlerVersion": "1.9",
        "autoUpgradeMinorVersion": true,
        "settings": {
          "fileUris": []
        },
        "protectedSettings": {
          "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -Command \"& { $secureDeployExecPassword = ConvertTo-SecureString -String ', variables('quote'), parameters('deployExecPassword'), variables('quote'), ' -AsPlainText -Force; New-LocalUser -AccountNeverExpires -UserMayNotChangePassword -Name ', variables('quote'), parameters('deployExecUsername'), variables('quote'), ' -Password $secureDeployExecPassword -FullName ', variables('quote'), parameters('deployExecUsername'), variables('quote'), ' -Description ', variables('quote'), 'Deployment account for Web Deploy', variables('quote'), ' -ErrorAction Continue ', '}\"')]"
        }
      }
    }

А затем , используя зависящий от , чтобы принудительно запустить расширение настраиваемого сценария перед DSC, установив их в расширении DSC.

      "dependsOn": [
        "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]",
        "addWindowsAccounts"
      ],

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

0 голосов
/ 30 ноября 2018

Вот один из последних, которые я использую:

Конфигурация:

Param(
    [System.Management.Automation.PSCredential]$Admincreds,
)

и в шаблоне я делаю это:

"publisher": "Microsoft.Powershell",
"type": "DSC",
"typeHandlerVersion": "2.20",
"autoUpgradeMinorVersion": true,
"settings": {
    "configuration": {
        "url": "url.zip",
        "script": "file.ps1",
        "function": "configuration"
    }
},
"protectedSettings": {
    "configurationArguments": {
        "adminCreds": {
            "userName": "username",
            "password": "password"
        }
    }
}

Вы не делаетенужно PSDscAllowPlainTextPassword, потому что они автоматически шифруются расширением powershell.dsc.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...