Skip to content

Instantly share code, notes, and snippets.

@omiossec
Created September 11, 2020 10:46
Show Gist options
  • Select an option

  • Save omiossec/e64ec646668d8dda05502f07f9dbf11a to your computer and use it in GitHub Desktop.

Select an option

Save omiossec/e64ec646668d8dda05502f07f9dbf11a to your computer and use it in GitHub Desktop.
<#
.SYNOPSIS
Create Azure Application, Certificate, SP and link them to Azure Automation Account as Run As Account
.DESCRIPTION
Create Azure Application, Certificate, SP and link them to Azure Automation Account as Run As Account
.PARAMETER ResourceGroupName
Name of the resource group where are located Automation Account and Keyvault
.PARAMETER AutomationAccount
Automation Account Name
.PARAMETER KeyVaultName
Keyvault name
.PARAMETER RunAsName
RunAs Account Name
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$ResourceGroupName,
[Parameter(Mandatory = $true)]
[string]
$AutomationAccount,
[Parameter(Mandatory = $true)]
[string]
$KeyVaultName
)
$RunAsAccountName = "$($AutomationAccount)-runas"
$CertificatSubjectName = "CN=$($RunAsAccountName)"
$AzAppUniqueId = (New-Guid).Guid
$AzAdAppURI = "http://$($AutomationAccount)$($AzAppUniqueId)"
$AzureKeyVaultCertificatePolicy = New-AzKeyVaultCertificatePolicy -SubjectName $CertificatSubjectName -IssuerName "Self" -KeyType "RSA" -KeyUsage "DigitalSignature" -ValidityInMonths 12 -RenewAtNumberOfDaysBeforeExpiry 20 -KeyNotExportable:$False -ReuseKeyOnRenewal:$False
Add-AzKeyVaultCertificate -VaultName $keyvaultName -Name $RunAsAccountName -CertificatePolicy $AzureKeyVaultCertificatePolicy | out-null
do {
start-sleep -Seconds 20
} until ((Get-AzKeyVaultCertificateOperation -Name $RunAsAccountName -vaultName $keyvaultName).Status -eq "completed")
$PfxPassword = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 48| foreach-object {[char]$_})
$PfxFilePath = join-path -Path (get-location).path -ChildPath "cert.pfx"
start-sleep 30
$AzKeyVaultCertificatSecret = Get-AzKeyVaultSecret -VaultName $keyvaultName -Name $RunAsAccountName
$AzKeyVaultCertificatSecretBytes = [System.Convert]::FromBase64String($AzKeyVaultCertificatSecret.SecretValueText)
$certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
$certCollection.Import($AzKeyVaultCertificatSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $PfxPassword)
[System.IO.File]::WriteAllBytes($PfxFilePath, $protectedCertificateBytes)
$AzADApplicationRegistration = New-AzADApplication -DisplayName $RunAsAccountName -HomePage "http://$($RunAsAccountName)" -IdentifierUris $AzAdAppURI
$AzKeyVaultCertificatStringValue = [System.Convert]::ToBase64String($certCollection.GetRawCertData())
$AzADApplicationCredential = New-AzADAppCredential -ApplicationId $AzADApplicationRegistration.ApplicationId -CertValue $AzKeyVaultCertificatStringValue -StartDate $certCollection.NotBefore -EndDate $certCollection.NotAfter
$AzADServicePrincipal = New-AzADServicePrincipal -ApplicationId $AzADApplicationRegistration.ApplicationId -SkipAssignment
$PfxPassword = ConvertTo-SecureString $PfxPassword -AsPlainText -Force
New-AzAutomationCertificate -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Path $PfxFilePath -Name "AzureRunAsCertificate" -Password $PfxPassword -Exportable:$Exportable
$ConnectionFieldData = @{
"ApplicationId" = $AzADApplicationRegistration.ApplicationId
"TenantId" = (Get-AzContext).Tenant.ID
"CertificateThumbprint" = $certCollection.Thumbprint
"SubscriptionId" = (Get-AzContext).Subscription.ID
}
New-AzAutomationConnection -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Name "AzureRunAsConnection" -ConnectionTypeName "AzureServicePrincipal" -ConnectionFieldValues $ConnectionFieldData
@ColinEff

Copy link
Copy Markdown

Hi @t8mas062184

Snippet from code I used to get around the 'incomplete' issue. It uses a custom role I created for manipulating VMs. Replace the role name with an existing role or custom role. Importantly, a role has to be assigned to the RunAs account

#// -------------------------------------------------------------------   
#// Define automation account variables
#// -------------------------------------------------------------------

$azEnvironment = [PSCustomObject]@{
    #// Global Settings
        Environment                 = "Production"

        SubscriptionId              = "11111111-2222-3333-4444-555555555555"
        Location                    = "Australia East"
        ResourceGroupName           = "company-AUE-PRD-ITOPS"
        AutomationAccountName       = "company-AUE-PRD-ITOPS-AA"
        CustomRoleName              = "Virtual Machine Operations (Custom)" # custom role with perms to manage VM state
        CustomRoleTempFile          = "$scriptPath\az-role.json"
        RunBookName                 = "ITOPS-category-scriptpurpose"
        RunBookScript               = "$scriptPath\az-script.ps1"
        RunBookScriptDependencies   = @("Az.Accounts","Az.Compute","Az.Resources")
        AssignCustomRole            = $False
        KeyVaultName                = "company-AUE-PRD-ITOPS-KV" # Key Vault Name, 24 chars max


        #// Tags for resources (LocalTags) and the Resource Group (GlobalTags)
        GlobalTags                  = @{Environment="Production";Owner="IT Operations Team";Purpose="IT Operations, Management and Automation";Production="True"}

    }

#// -------------------------------------------------------------------   
#// Create RunAs Account
#// -------------------------------------------------------------------
if ($azEnvironment.createRunAsAccount) {
    Write-host "[INFO]  Creating KeyVault and RunAs Account for Runbook $($azEnvironment.RunBookName)"
    $result = Generate-RunAsAccount -ResourceGroupName $azEnvironment.ResourceGroupName `
              -AutomationAccountName $azEnvironment.AutomationAccountName `
              -Location $azEnvironment.Location `
              -KeyVaultName $azEnvironment.KeyVaultName `
              -KeyVaultTag $azEnvironment.GlobalTags
              
    $result
} else {
    Write-host "[INFO]  Disabled. Skipped creating KeyVault and RunAs Account for Runbook $($azEnvironment.RunBookName)"
}

#// -------------------------------------------------------------------
#// Assign Custom Role to Automation Account RunAs Account (replace contributor role)
#// -------------------------------------------------------------------
if ($azEnvironment.assignCustomRole) {
    $Confirm = $False
    Write-host "[INFO]  Assiging custom role $($azEnvironment.CustomRoleName) to RunAs account for AA $($azEnvironment.AutomationAccountName)"

    # Force replace Contributor role if set to True
    $ReplaceCustomRoleAssignment = $True

    # Retrieve Automation Account
    $AutomationAccount = Get-AzAutomationAccount -Name $azEnvironment.AutomationAccountName -ResourceGroupName $azEnvironment.ResourceGroupName

    # Retrieve Automation Account App ID
    $runasAccountAADAplicationId = Get-RunAsAccountAADApplicationId -ResourceGroupName $AutomationAccount.ResourceGroupName -AutomationAccountName $AutomationAccount.AutomationAccountName

    # Replace role assignment
    if ($runasAccountAADAplicationId) { 
        $subscriptionScope = "/subscriptions/" + $azEnvironment.SubscriptionId
        #$subscriptionScope = $SubscriptionPath


        # Retrieve existing role assignments
        if ($ReplaceCustomRoleAssignment -eq $true) {
            $currentRoleAssignments = Get-AzRoleAssignment `
                -ServicePrincipalName $runasAccountAADAplicationId `
                -Scope $subscriptionScope `
                -ErrorAction Stop
        } else {
            # Retrieve 'assumed' contributor role assignment if ReplaceCustomRoleAssignment is false
            $currentRoleAssignments = Get-AzRoleAssignment `
                -ServicePrincipalName $runasAccountAADAplicationId `
                -RoleDefinitionName "Contributor" `
                -Scope $subscriptionScope `
                -ErrorAction Stop
        }

        Write-Host "[INFO]  Role assignments in this automation account:"
        $currentRoleAssignments



        # Check if contributor role is already assigned, if so nothing to do, exit
        $hasContributorRoleAssignment = $false
        foreach ($roleAssignment in $currentRoleAssignments) {
            if ($roleAssignment.RoleDefinitionName -eq "Contributor") {
                $hasContributorRoleAssignment = $true
                Break
            }
        }

        # Start process to replace existing role with contributor role
        if (($hasContributorRoleAssignment -eq $true) -or ($ReplaceCustomRoleAssignment -eq $true)) {
            Write-Host ("[INFO]  Deleting existing role assignments from " + $automationAccount.AutomationAccountName)
            foreach ($roleAssignment in $currentRoleAssignments)
            {
                if (($roleAssignment.RoleDefinitionName -eq "Contributor") -or ($ReplaceCustomRoleAssignment -eq $true))
                {
                    Remove-AzRoleAssignment -InputObject $roleAssignment -ErrorAction Stop
                    Write-Host ("[INFO]  Deleted role assignment to " + $roleAssignment.RoleDefinitionName)
                }
            }

            Write-Host ("[INFO]  Creating new role assignment for " + $automationAccount.AutomationAccountName)
            $customRoleAssignment = New-AzRoleAssignment `
                                -RoleDefinitionName $azEnvironment.CustomRoleName `
                                -ServicePrincipalName $runasAccountAADAplicationId `
                                -ErrorAction Stop
        
            Write-Host "[INFO]  Created a new role assignment:" -ForegroundColor Green
            $customRoleAssignment

        } else {
            Write-Host ("[ERROR] Automation account: " + $automationAccount.AutomationAccountName + " was not changed because Contributor role was not assigned to RunAs account.") -ForegroundColor Yellow
            Write-Host 
        }
    } else {
        Write-Host  ("[ERROR] Automation account: " + $AutomationAccount.AutomationAccountName + " was not changed because RunAs account does not exist.") -ForegroundColor Yellow
        Write-Host
    }    
}

@t8mas062184

Copy link
Copy Markdown

Thank you for your prompt response, @ColinEff! Creating RunAs Account is my first task in Azure and I find it challenging. This would definitely be a big help. I am creating my Automation Account via Ansible playbook using azure_rm_automationaccount (azcollections) but it lacks the functionality to create the RunAs Account. I'll see how this would be integrated with my Ansible playbook I've formulated for Automation Account creation.

Cheers!

@JonRunheim

JonRunheim commented Feb 19, 2021

Copy link
Copy Markdown

Hi,

The script needs to be adjusted with the below to solve the deprecation of the .secretvaluetext

#THIS SECTION IS REPLACED ADDED AS THE .secretvaluetext OPERATOR IS DEPRECATED
#$AzKeyVaultCertificatSecretBytes = [System.Convert]::FromBase64String($AzKeyVaultCertificatSecret.SecretValueText)

#THE BELOW IS THE REPLACEMENT
$secretValueText = '';
$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzKeyVaultCertificatSecret.SecretValue)
try {
$secretValueText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)
} finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)
}
$AzKeyVaultCertificatSecretBytes = [Convert]::FromBase64String($secretValueText)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment