Framework/Core/ContinuousCompliance/CCAutomation.ps1
using namespace System.Management.Automation Set-StrictMode -Version Latest class CCAutomation: CommandBase { hidden [AutomationAccount] $AutomationAccount hidden [Runbook[]] $Runbooks = @() hidden [string] $RunbookName = "Continuous_Compliance_Runbook" hidden [RunbookSchedule[]] $RunbookSchedules = @() hidden [string] $ScheduleName = "Scan_Schedule" hidden [Variable[]] $Variables = @() hidden [UserConfig] $UserConfig hidden [string] $AzSDKCCRGName = "AzSDKCCRG" hidden [string] $deprecatedAccountName = "AzSDKCCAutomationAccount" hidden [PSObject] $OutputObject = @{} hidden [SelfSignedCertificate] $certificateDetail = [SelfSignedCertificate]::new() hidden [Hashtable] $reportStorageTags = @{} hidden [string] $exceptionMsg = "There was an error while configuring Automation Account." hidden [boolean] $isExistingADApp = $false hidden [boolean] $cleanupFlag = $true hidden [string] $updateCommandName = "Update-AzSDKContinuousAssurance" hidden [string] $removeCommandName = "Remove-AzSDKContinuousAssurance" hidden [string] $installCommandName = "Install-AzSDKContinuousAssurance" hidden [string] $certificateAssetName = "AzureRunAsCertificate" hidden [string] $connectionAssetName = "AzureRunAsConnection" CCAutomation( [string] $subscriptionId, [InvocationInfo] $invocationContext, [string] $AutomationAccountLocation,` [string] $ResourceGroupNames,` [string] $OMSWorkspaceId,` [string] $OMSSharedKey,` [string] $AzureADAppName) : Base($subscriptionId, $invocationContext) { $this.AutomationAccount = [AutomationAccount]@{ Name = "AzSDKContinuousAssurance"; Location = $AutomationAccountLocation; AzureADAppName = $AzureADAppName } $this.UserConfig = [UserConfig]@{ OMSCredential = [OMSCredential]@{ OMSWorkspaceId = $OMSWorkspaceId; OMSSharedKey = $OMSSharedKey }; ResourceGroupNames = $ResourceGroupNames } } CCAutomation( [string] $subscriptionId, [InvocationInfo] $invocationContext) : Base($subscriptionId, $invocationContext) { $this.AutomationAccount = [AutomationAccount]@{ Name = "AzSDKContinuousAssurance" } } [MessageData[]] InstallAzSDKContinuousAssurance() { [MessageData[]] $messages = @(); try { #region :check if resource provider is registered if((Get-AzureRmResourceProvider -ProviderNamespace "Microsoft.Automation" ` -Location $this.AutomationAccount.Location -ErrorAction Stop | Measure-Object).Count -eq 0) { $this.cleanupFlag = $false throw ($this.exceptionMsg + "Resource provider 'Microsoft.Automation' is not registered under location " + $this.AutomationAccount.Location) } #endregion #region :check if older CC version is installed and remove if exists and code to be removed after 06/30/2017 # $this.DeleteResourceGroup($this.AzSDKCCRGName); #endregion #region :create new resource group/check if RG exists# $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName if((Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0) { $this.PublishCustomMessage("Started setting up Automation Account for Continuous Assurance"); $this.NewCCResourceGroup() } else { #check if automation account exists in RG and code to be updated after 06/30/2017 $existingAccount = Find-AzureRMresource -ResourceGroupName $this.AutomationAccount.ResourceGroup -ResourceType "Microsoft.Automation/automationAccounts" | where-object{$_.ResourceName -in ($this.AutomationAccount.Name,$this.deprecatedAccountName)} if(($existingAccount|Measure-Object).Count -gt 0) { $existingAccount | ForEach-Object{ $tags = $_.Tags #check if depricated version found (old accounts don't have tags) if($_.ResourceName-eq $this.deprecatedAccountName -or ` $tags.Count -eq 0 -or !$tags.Contains("AzSDKVersion") -or ` ([System.Version]$tags["AzSDKVersion"] -lt [System.Version]([ConfigurationManager]::GetAzSdkConfigData().UpdateCompatibleCCVersion))) { #remove depricated version $this.RemoveAzSDKContinuousAssurance($this.Context.SubscriptionId) } else { $this.cleanupFlag = $false #need to run update command throw("Automation Account [$($this.AutomationAccount.Name)] for Continuous Assurance already exists in subscription. If you want to update existing account, please run command '"+$this.updateCommandName+"' with required parameters") } } } else { #update tags to existing RG $this.PublishCustomMessage("Started setting up Automation Account for Continuous Assurance"); $this.AutomationAccount.RGTags += @{ "AzSDKFeature" = "ContinuousAssurance"; "AzSDKVersion"=$this.GetCurrentModuleVersion(); "CreationTime"=$(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss"); } Set-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup ` -Tag $this.AutomationAccount.RGTags ` -ErrorAction Stop $this.OutputObject.ResourceGroup = $null } } #endregion #region: Deploy empty Automation account $this.PublishCustomMessage("Creating Automation Account - [" + $this.AutomationAccount.Name + "]") $this.NewEmptyAutomationAccount() #endregion #region: Create SPN, Certificate $this.NewCCAzureRunAsAccount() #endregion #region: Create/reuse existing storage account (Added this before creating variables since it's value is used in it) $this.UserConfig.StorageAccountRG = $this.AutomationAccount.ResourceGroup $existingStorage = $this.CheckContinuousAssuranceStorage() if(($existingStorage|Measure-Object).Count -gt 0) { $this.UserConfig.StorageAccountName = $existingStorage.ResourceName $this.PublishCustomMessage("Found existing storage account ["+ $this.UserConfig.StorageAccountName +"], it will be used to store Continuous Assurance output reports") } else { #create new storage $this.UserConfig.StorageAccountName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss")) $this.PublishCustomMessage("Creating storage account ["+ $this.UserConfig.StorageAccountName +"] to store Continuous Assurance output reports") $newStorage = [Helpers]::NewAzsdkCompliantStorage($this.UserConfig.StorageAccountName,$this.UserConfig.StorageAccountRG, $this.AutomationAccount.Location) if(!$newStorage) { $this.cleanupFlag = $false throw ($this.exceptionMsg + "Failed to create AzSDK compliant storage account for output reports storage. Please run command again.") } else { #apply tags $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss") $this.reportStorageTags += @{ "AzSDKFeature" = "ContinuousAssuranceStorage"; "CreationTime"=$timestamp; "LastModified"=$timestamp } Set-AzureRmStorageAccount -ResourceGroupName $newStorage.ResourceGroupName -Name $newStorage.StorageAccountName -Tag $this.reportStorageTags -Force -ErrorAction SilentlyContinue } } $this.OutputObject.StorageAccountName = $this.UserConfig.StorageAccountName #endregion #region: Deploy Automation account items (runbooks, variables, schedules) $this.DeployCCAutomationAccountItems() #endregion #succefully installed $this.cleanupFlag = $false $this.PublishCustomMessage("Completed setting up Automation Account for Continuous Assurance") $messages += [MessageData]::new("Below resources are created in resource group ["+$this.AutomationAccount.ResourceGroup+"] as part of Continuous Assurance") $messages += [MessageData]::new($this.OutputObject.AutomationAccount) $messages += [MessageData]::new($this.OutputObject.Runbooks) $messages += [MessageData]::new($this.OutputObject.Variables) $messages += [MessageData]::new($this.OutputObject.Schedules) $messages += [MessageData]::new($this.OutputObject.AzureADAppName) $messages += [MessageData]::new($this.OutputObject.AzureRunAsConnection) $messages += [MessageData]::new($this.OutputObject.StorageAccountName) } catch { #cleanup if exception occurs if($this.cleanupFlag) { $this.PublishCustomMessage("Error occured. Rollbacking the changes.") #$this.DeleteResourceGroup($this.AutomationAccount.ResourceGroup); $account = Get-AzureRMAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.AutomationAccount.Name -ErrorAction silentlycontinue if(($account|Measure-Object).Count -gt 0) { $account | Remove-AzureRmAutomationAccount -Force -ErrorAction SilentlyContinue } if(!$this.isExistingADApp) { #clean AD App only if AD App was newly created $ADApplication = Get-AzureRmADApplication -DisplayNameStartWith $this.AutomationAccount.AzureADAppName -ErrorAction SilentlyContinue | Where-Object -Property DisplayName -eq $this.AutomationAccount.AzureADAppName if($ADApplication) { Remove-AzureRmADApplication -ObjectId $ADApplication.ObjectId -Force -ErrorAction Stop } } } $this.PublishException($_) } return $messages; } [MessageData[]] UpdateAzSDKContinuousAssurance($ResourceGroupNames,$OMSWorkspaceId,$OMSSharedKey,$AzureADAppName,$UpdateCertificate) { [MessageData[]] $messages = @(); #set default account properties $this.AutomationAccount.Name = "AzSDKContinuousAssurance" $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName #region :check if automation account is compatible for update $existingAccount = Find-AzureRMresource -ResourceGroupName $this.AutomationAccount.ResourceGroup -ResourceType "Microsoft.Automation/automationAccounts" | where-object{$_.ResourceName -in ($this.AutomationAccount.Name,$this.deprecatedAccountName)} $automationTags = @() if(($existingAccount|Measure-Object).Count -gt 0) { #check if depricated version found (old accounts don't have tags) $existingAccount | ForEach-Object{ $automationTags = $_.Tags if($automationTags.Count -eq 0 -or !$automationTags.Contains("AzSDKVersion") -or ` ([System.Version]$automationTags["AzSDKVersion"] -lt [System.Version]([ConfigurationManager]::GetAzSdkConfigData().UpdateCompatibleCCVersion))) { throw("Deprecated and uncompatible version of Continuous Assurance Automation Account [$($_.ResourceName)] found. Please remove this account using '"+$this.removeCommandName+"' command and install latest version using '"+$this.installCommandName+"' command with required parameters.") } } } else { throw("No Continuous Assuarance Automation Account found. Please install using '"+ $this.installCommandName +"' command with required parameters.") } #endregion $this.PublishCustomMessage("Started updating Automation Account for Continuous Assurance"); #region :Remove existing and create new AzureRunAsConnection if AzureADAppName param is passed else update certificate if UpdateCertificate switch is present if(![string]::IsNullOrWhiteSpace($AzureADAppName)) { $this.PublishCustomMessage("Updating $($this.connectionAssetName) in Automation Account") $this.RemoveCCAzureRunAsAccount() $this.RemoveCCAzureRunAsCertificate() $this.AutomationAccount.AzureADAppName = $AzureADAppName $this.NewCCAzureRunAsAccount() } elseif($UpdateCertificate) { $this.PublishCustomMessage("Updating certificate in $($this.connectionAssetName)") $this.RemoveCCAzureRunAsCertificate() $this.UpdateCCAzureRunAsAccount() } #endregion #region: create storage account if not present and update same in variables# $this.OutputObject.Variables = @() #This is added to initialize variables $newStorageName = $null #check if storage exists $existingStorage = $this.CheckContinuousAssuranceStorage() if(($existingStorage|Measure-Object).Count -gt 0) { #update/add tags $modifyTimestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss") $storageTags = $existingStorage.Tags if($storageTags.ContainsKey("LastModified")) { $storageTags["LastModified"] = $modifyTimestamp; } else { $storageTags.Add("LastModified",$modifyTimestamp) } if(!$storageTags.ContainsKey("AzSDKFeature")) { $storageTags.Add("AzSDKFeature","ContinuousAssuranceStorage") } if(!$storageTags.ContainsKey("CreationTime")) { $storageTags.Add("CreationTime",$modifyTimestamp) } Set-AzureRmStorageAccount -ResourceGroupName $existingStorage.ResourceGroupName -Name $existingStorage.ResourceName -Tag $storageTags -Force -ErrorAction SilentlyContinue } else { #create default storage $newStorageName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss")) $this.PublishCustomMessage("Creating storage account [$newStorageName] to store Continuous Assurance output reports") $newStorage = [Helpers]::NewAzsdkCompliantStorage($newStorageName, $this.AutomationAccount.ResourceGroup, $existingAccount.Location) if(!$newStorage) { throw ($this.exceptionMsg + "Failed to create AzSDK compliant storage account for output reports storage. Please run command again.") } else { #apply tags $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss") $this.reportStorageTags += @{ "AzSDKFeature" = "ContinuousAssuranceStorage"; "CreationTime"=$timestamp; "LastModified"=$timestamp } Set-AzureRmStorageAccount -ResourceGroupName $newStorage.ResourceGroupName -Name $newStorage.StorageAccountName -Tag $this.reportStorageTags -Force -ErrorAction SilentlyContinue } #update storage account variable with new value $varStorageName = [Variable]@{ Name = "ReportsStorageAccountName"; Value = $newStorage.StorageAccountName; IsEncrypted = $false; Description ="Name of storage account where output reports will be stored" } $this.UpdateVariable($varStorageName) $this.OutputObject.StorageAccountName = $newStorageName } #endregion #region :update user configurable variables which are present in params if(![string]::IsNullOrWhiteSpace($OMSWorkspaceId)) { $varOmsWSID = [Variable]@{ Name = "OMSWorkspaceId"; Value = $OMSWorkspaceId; IsEncrypted = $true } $this.UpdateVariable($varOmsWSID) $this.PublishCustomMessage("Updating variable ["+$varOmsWSID.Name+"]") } if(![string]::IsNullOrWhiteSpace($OMSSharedKey)) { $varOMSSharedKey = [Variable]@{ Name = "OMSSharedKey"; Value = $OMSSharedKey; IsEncrypted = $true } $this.UpdateVariable($varOMSSharedKey) $this.PublishCustomMessage("Updating variable ["+$varOMSSharedKey.Name+"]") } if(![string]::IsNullOrWhiteSpace($ResourceGroupNames)) { $varAppRG = [Variable]@{ Name = "AppResourceGroupNames"; Value = $ResourceGroupNames; IsEncrypted = $false } $this.UpdateVariable($varAppRG) $this.PublishCustomMessage("Updating variable ["+$varAppRG.Name+"]") } #endregion #region :update runbook & schedule #unlink runbook from existing schedules $scheduledRunbooks = Get-AzureRmAutomationScheduledRunbook -AutomationAccountName $this.AutomationAccount.Name ` -ResourceGroupName $this.AutomationAccount.ResourceGroup | Where-Object {$_.RunbookName -eq $this.RunbookName} if(($scheduledRunbooks|Measure-Object).Count -gt 0) { $scheduledRunbooks | ForEach-Object { Unregister-AzureRmAutomationScheduledRunbook -RunbookName $_.RunbookName -ScheduleName $_.ScheduleName ` -ResourceGroupName $_.ResourceGroupName ` -AutomationAccountName $_.AutomationAccountName -ErrorAction Stop -Force | Out-Null }; } #remove existing and create new runbook $existingRunbook = Get-AzureRmAutomationRunbook -AutomationAccountName $this.AutomationAccount.Name ` -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -Name $this.RunbookName if(($existingRunbook|Measure-Object).Count -gt 0) { $existingRunbook | Remove-AzureRmAutomationRunbook -Force -ErrorAction Stop } $this.PublishCustomMessage("Updating runbook - ["+ $this.RunbookName +"]") $this.NewCCRunbooks() #relink existing schedules with runbook $this.PublishCustomMessage("Linking schedule - ["+$this.ScheduleName+"] to the runbook") if(($scheduledRunbooks|Measure-Object).Count -gt 0) { $scheduledRunbooks | ForEach-Object { Register-AzureRmAutomationScheduledRunbook -RunbookName $this.RunbookName -ScheduleName $_.ScheduleName ` -ResourceGroupName $_.ResourceGroupName ` -AutomationAccountName $_.AutomationAccountName -ErrorAction Stop | Out-Null }; } else { #create default schedule $this.NewCCSchedules() } #endregion #region :update last modified and version tag $modifyTimestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss") if($automationTags.ContainsKey("LastModified")) { $automationTags["LastModified"] = $modifyTimestamp; } else { $automationTags.Add("LastModified",$modifyTimestamp) } if($automationTags.ContainsKey("AzSDKVersion")) { $automationTags["AzSDKVersion"] = $this.GetCurrentModuleVersion(); } else { $automationTags.Add("AzSDKVersion",$this.GetCurrentModuleVersion()) } Set-AzureRmAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.AutomationAccount.Name -Tags $automationTags -ErrorAction SilentlyContinue #endregion $this.PublishCustomMessage("Completed updating Automation Account for Continuous Assurance") $messages += [MessageData]::new("Below resources are updated in your subscription", $this.OutputObject) return $messages; } [MessageData[]] GetAzSDKContinuousAssurance() { [MessageData[]] $messages = @(); $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName #Fetch automation account components $this.OutputObject.StorageAccount = Find-AzureRmResource -ResourceGroupName $this.AutomationAccount.ResourceGroup -ResourceNameContains "azsdk" -ResourceType "Microsoft.Storage/storageAccounts" $this.OutputObject.Variables = Get-AzureRmAutomationVariable -AutomationAccountName $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup $this.OutputObject.AutomationAccount = Get-AzureRmAutomationAccount -Name $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup $this.OutputObject.Runbooks = Get-AzureRmAutomationRunbook -AutomationAccountName $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup $this.OutputObject.Schedules = Get-AzureRmAutomationSchedule -AutomationAccountName $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup $this.OutputObject.AzureRunAsConnection = Get-AzureRmAutomationConnection -AutomationAccountName $this.AutomationAccount.Name -Name $this.connectionAssetName -ResourceGroupName $this.AutomationAccount.ResourceGroup $this.OutputObject.AzureRunAsCertificate = Get-AzureRmAutomationCertificate -AutomationAccountName $this.AutomationAccount.Name -Name $this.certificateAssetName -ResourceGroupName $this.AutomationAccount.ResourceGroup $messages += [MessageData]::new("Below resources/automation account assets are present in subscription as part of Continuous Assurance", $this.OutputObject) $this.PublishCustomMessage($messages) return $messages } [MessageData[]] RemoveAzSDKContinuousAssurance($DeleteReportsStorageAccount) { [MessageData[]] $messages = @(); $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName #filter accounts with old/new name $accounts = Get-AzureRMAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -ErrorAction silentlycontinue | where-object{$_.AutomationAccountName -in ($this.AutomationAccount.Name,$this.deprecatedAccountName)} if($accounts) { $accounts | ForEach-Object{ Remove-AzureRmAutomationAccount -ResourceGroupName $_.ResourceGroupName -name $_.AutomationAccountName -confirm -ErrorAction stop|Out-Null $messages += [MessageData]::new("Removed Automation Account [$($_.AutomationAccountName)] from resource group [$($this.AutomationAccount.ResourceGroup)]") } } if($DeleteReportsStorageAccount) { $existingStorage = $this.CheckContinuousAssuranceStorage() if(($existingStorage|Measure-Object).Count -gt 0) { $messages += [MessageData]::new("You have chosen to delete storage account [$($existingStorage.ResourceName)]. Removing storage account will delete the existing content (AzSDK data like reports etc) permanently. Please backup data if required.",[MessageType]::Warning) Remove-AzureRmStorageAccount -ResourceGroupName $existingStorage.ResourceGroupName -Name $existingStorage.ResourceName -Confirm -ErrorAction SilentlyContinue | Out-Null $messages += [MessageData]::new("Removed [$($existingStorage.ResourceName)] storage account from resource group [$($this.AutomationAccount.ResourceGroup)]") } } #remove job collection if exists to make compatible with old accounts $jobCollectionName = "AzSDKCCJobCollection" $jobCollection = Get-AzureRmSchedulerJobCollection -ResourceGroupName $this.AutomationAccount.ResourceGroup -JobCollectionName $jobCollectionName -ErrorAction SilentlyContinue if($jobCollection) { $jobCollection | Remove-AzureRmSchedulerJobCollection -Confirm -ErrorAction SilentlyContinue | Out-Null $messages += [MessageData]::new("Removed scheduler job collection [$jobCollectionName] from resource group [$($this.AutomationAccount.ResourceGroup)]", $this.OutputObject.JobCollection) } return $messages } #region: Internal functions for install account hidden [void] DeleteResourceGroup($resourceGroupName) { if((Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0) { Remove-AzureRmResourceGroup -Name $resourceGroupName -Force -ErrorAction Stop | Out-Null } } hidden [void] NewCCResourceGroup() { $this.AutomationAccount.RGTags += @{ "AzSDKFeature" = "ContinuousAssurance"; "AzSDKVersion"=$this.GetCurrentModuleVersion(); "CreationTime"=$(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss"); } $newRG = New-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -Location $this.AutomationAccount.Location ` -Tag $this.AutomationAccount.RGTags ` -ErrorAction Stop $this.OutputObject.ResourceGroup = $newRG | Select-Object ResourceGroupName,Location } hidden [void] DeployCCAutomationAccountItems() { #Adding below to make schedule available for adding in runbook config $ScanSchedule = [RunbookSchedule]@{ Name = $this.ScheduleName; Frequency = [ScheduleFrequency]::Day; Interval = 1; Description = "Scheduling job to scan subscription and app resource groups"; StartTime = ([System.DateTime]::Now.AddMinutes(10).ToString("yyyy-MM-dd'T'HH:mm:sszzz")); LinkedRubooks = @($this.RunbookName); Key = "Scan_Schedule" } $this.RunbookSchedules += @($ScanSchedule) $this.PublishCustomMessage("Creating runbook - ["+ $this.RunbookName +"]") $this.NewCCRunbooks() $this.PublishCustomMessage("Linking schedule - ["+$this.ScheduleName+"] to the runbook") $this.NewCCSchedules() $this.PublishCustomMessage("Creating variables") $this.NewCCVariables() } hidden [void] NewEmptyAutomationAccount() { #Add tags $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss") $this.AutomationAccount.AccountTags += @{ "AzSDKFeature" = "ContinuousAssurance"; "AzSDKVersion"=$this.GetCurrentModuleVersion(); "CreationTime"=$timestamp; "LastModified"=$timestamp } $this.OutputObject.AutomationAccount = New-AzureRmAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -Name $this.AutomationAccount.Name -Location $this.AutomationAccount.Location ` -Plan Basic -Tags $this.AutomationAccount.AccountTags -ErrorAction Stop | Select-Object ResourceGroupName,AutomationAccountName,Location,State,Plan } hidden [void] NewCCRunbooks() { $CCRunbook = [Runbook]@{ Name = $this.RunbookName; Type = "PowerShell"; Description = "This runbook is responsible for running SVT and SS-Health commands and keep all modules updated"; LogProgress = $false; LogVerbose = $false; Key="Continuous_Compliance_Runbook" } $this.Runbooks += @($CCRunbook) $this.Runbooks | ForEach-Object{ $filePath = $this.AddConfigValues($_.Name+".ps1"); Import-AzureRmAutomationRunbook -Name $_.Name -Description $_.Description -Type $_.Type ` -Path $filePath ` -LogProgress $_.LogProgress -LogVerbose $_.LogVerbose ` -AutomationAccountName $this.AutomationAccount.Name ` -ResourceGroupName $this.AutomationAccount.ResourceGroup -Published -ErrorAction Stop #cleanup Remove-Item -Path $filePath -Force } $this.OutputObject.Runbooks = $this.Runbooks } hidden [void] NewCCSchedules() { if($this.RunbookSchedules.count -eq 0) { $ScanSchedule = [RunbookSchedule]@{ Name = $this.ScheduleName; Frequency = [ScheduleFrequency]::Day; Interval = 1; Description = "Scheduling job to scan subscription and app resource groups"; StartTime = ([System.DateTime]::Now.AddMinutes(10).ToString("yyyy-MM-dd'T'HH:mm:sszzz")); LinkedRubooks = @($this.RunbookName); Key = "Scan_Schedule" } $this.RunbookSchedules += @($ScanSchedule) } $this.RunbookSchedules | ForEach-Object{ $scheduleName = $_.Name New-AzureRmAutomationSchedule -AutomationAccountName $this.AutomationAccount.Name -Name $_.Name ` -ResourceGroupName $this.AutomationAccount.ResourceGroup -StartTime $_.StartTime ` -Description $_.Description -DayInterval $_.Interval -ErrorAction Stop $_.LinkedRubooks | ForEach-Object{ Register-AzureRmAutomationScheduledRunbook -RunbookName $_ -ScheduleName $scheduleName ` -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -ErrorAction Stop } } $this.OutputObject.Schedules = $this.RunbookSchedules } hidden [void] NewCCVariables() { $varAppRG = [Variable]@{ Name = "AppResourceGroupNames"; Value = $this.UserConfig.ResourceGroupNames; IsEncrypted = $false; Description ="Comma separated values of the different resource groups that has to be scanned" } $varOmsWSID = [Variable]@{ Name = "OMSWorkspaceId"; Value = $this.UserConfig.OMSCredential.OMSWorkspaceId; IsEncrypted = $true; Description ="OMS Workspace Id" } $varOmsWSKey = [Variable]@{ Name = "OMSSharedKey"; Value = $this.UserConfig.OMSCredential.OMSSharedKey; IsEncrypted = $true; Description ="OMS Workspace Shared Key" } $varStorageName = [Variable]@{ Name = "ReportsStorageAccountName"; Value = $this.UserConfig.StorageAccountName; IsEncrypted = $false; Description ="Name of storage account where output reports will be stored" } $this.Variables += @($varAppRG,$varOmsWSID,$varOmsWSKey,$varStorageName) $this.Variables|ForEach-Object{ New-AzureRmAutomationVariable -Name $_.Name -Encrypted $_.IsEncrypted ` -Description $_.Description -Value $_.Value ` -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -ErrorAction Stop $this.PublishCustomMessage("Name : "+$_.Name) } $this.OutputObject.Variables = $this.Variables } hidden [void] NewCCAzureRunAsAccount() { $pfxFilePath = $null $thumbPrint = $null $ADApplication = $null try { $ADApplication = Get-AzureRmADApplication -DisplayNameStartWith $this.AutomationAccount.AzureADAppName | Where-Object -Property DisplayName -eq $this.AutomationAccount.AzureADAppName if($ADApplication) { $this.PublishCustomMessage("Found azure active directory application - ["+ $this.AutomationAccount.AzureADAppName +"]") #set this flag to identify whether clean up AD App is needed in case of exception $this.isExistingADApp = $true } else { $this.PublishCustomMessage("Creating new azure active directory application - ["+ $this.AutomationAccount.AzureADAppName +"]") #create new AD App $ADApplication = New-AzureRmADApplication -DisplayName $this.AutomationAccount.AzureADAppName ` -HomePage ("https://" + $this.AutomationAccount.AzureADAppName) ` -IdentifierUris ("https://" + $this.AutomationAccount.AzureADAppName) -ErrorAction Stop #create new SP $this.PublishCustomMessage("Creating new service principal") New-AzureRMADServicePrincipal -ApplicationId $ADApplication.ApplicationId -ErrorAction Stop | Out-Null } $selfsignedCertificate = [ActiveDirectoryHelper]::NewSelfSignedCertificate($this.AutomationAccount.AzureADAppName,$this.certificateDetail.CertStartDate,$this.certificateDetail.CertEndDate,$this.certificateDetail.Provider) #create password $secureCertPassword = [ActiveDirectoryHelper]::NewSecurePassword() $pfxFilePath = $env:TEMP+ "\temp.pfx" Export-PfxCertificate -Cert $selfsignedCertificate -Password $secureCertPassword -FilePath $pfxFilePath | Out-Null $publicCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,$selfsignedCertificate.GetRawCertData()) #Authenticating AAD App service principal with newly created certificate credential [ActiveDirectoryHelper]::UpdateADAppCredential($ADApplication.ApplicationId,$publicCert,$this.certificateDetail.CredStartDate,$this.certificateDetail.CredEndDate,"False") $this.PublishCustomMessage("Adding service principal to Reader RBAC role at subscription level and Contributor RBAC role at "+ $this.AutomationAccount.ResourceGroup +" resource group level") $NewSPNRole = $null $RetryCount = 0; While ($null -eq $NewSPNRole -and $RetryCount -le 6) { #Assign RBAC to SPN - contributor at RG and reader at subscription level Start-Sleep 10 New-AzureRMRoleAssignment -RoleDefinitionName Reader -ServicePrincipalName $ADApplication.ApplicationId -ErrorAction SilentlyContinue | Out-Null New-AzureRMRoleAssignment -Scope (Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction Stop).ResourceId -RoleDefinitionName Contributor -ServicePrincipalName $ADApplication.ApplicationId -ErrorAction SilentlyContinue | Out-Null $NewSPNRole = Get-AzureRMRoleAssignment -ServicePrincipalName $ADApplication.ApplicationId -ErrorAction SilentlyContinue $RetryCount++; } $thumbPrint = $publicCert.thumbPrint.ToString() #create certificate asset $this.PublishCustomMessage("Adding certificate - ["+ $this.certificateAssetName +"] and connection - ["+ $this.connectionAssetName +"] in Automation Account") $newCertificateAsset = $this.NewCCCertificate($pfxFilePath,$secureCertPassword) # Create a Automation connection asset. This connection uses the service principal. $newConnectionAsset = $this.NewCCConnection($ADApplication.ApplicationId,$thumbPrint) $this.OutputObject.AzureADAppName = $this.AutomationAccount.AzureADAppName $this.OutputObject.AzureRunAsConnection = $newConnectionAsset | Select-Object ConnectionTypeName,FieldDefinitionValues,Name,Description } finally { #cleanup pfx file Remove-Item -Path $pfxFilePath -Force -ErrorAction SilentlyContinue #cleanup certificate $CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) $CertStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) if($thumbPrint) { $tempCert = $CertStore.Certificates.Find("FindByThumbprint",$thumbPrint,$FALSE) if($tempCert) { $CertStore.Remove($tempCert[0]) } } } } hidden [string] AddConfigValues([string]$fileName) { $outputFilePath = "$Env:LOCALAPPDATA\$fileName"; Get-Content "$PSScriptRoot\$fileName" -ErrorAction Stop | Foreach-Object { $temp1 = $_ -replace "\[#SubscriptionID#\]",$this.Context.SubscriptionId; $temp2 = $temp1 -replace "\[#AutomationAccountRG#\]",$this.AutomationAccount.ResourceGroup; $temp3 = $temp2 -replace "\[#AutomationAccountName#\]",$this.AutomationAccount.Name; $temp4 = $temp3 -replace "\[#RunbookName#\]",($this.Runbooks|Where-Object{$_.Key -eq "Continuous_Compliance_Runbook"}|Select-Object -ExpandProperty Name); $temp5 = $temp4 -replace "\[#ScanScheduleName#\]",($this.RunbookSchedules|Where-Object{$_.Key -eq "Scan_Schedule"}|Select-Object -ExpandProperty Name); $temp6 = $temp5 -replace "\[#UseOnlinePolicyStore#\]",$this.ConvertBooleanToString([ConfigurationManager]::GetAzSdkSettings().UseOnlinePolicyStore); $temp7 = $temp6 -replace "\[#OnlinePolicyStoreUrl#\]",[ConfigurationManager]::GetAzSdkSettings().OnlinePolicyStoreUrl; $temp7 -replace "\[#EnableAADAuthForOnlinePolicyStore#\]",$this.ConvertBooleanToString([ConfigurationManager]::GetAzSdkSettings().EnableAADAuthForOnlinePolicyStore) } | Out-File $outputFilePath return $outputFilePath } hidden [string] ConvertBooleanToString($boolvalue) { switch($boolvalue) { "true"{ return "true" } "false"{ return "false"} } return "false" #adding this to prevent error all path doesn't return value" } #endregion #region: Internal functions for update account hidden [void] UpdateVariable($VariableObj) { $updatedVariable = Set-AzureRmAutomationVariable -Name $VariableObj.Name ` -Encrypted $VariableObj.IsEncrypted ` -Value $VariableObj.Value ` -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -ErrorAction Stop $this.OutputObject.Variables += $updatedVariable } hidden [void] RemoveCCAzureRunAsAccount() { #remove existing azurerunasconnection if((Get-AzureRmAutomationConnection -Name $this.connectionAssetName -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -ErrorAction SilentlyContinue|Measure-Object).Count -gt 0) { Remove-AzureRmAutomationConnection -ResourceGroupName $this.AutomationAccount.ResourceGroup` -AutomationAccountName $this.AutomationAccount.Name -Name $this.connectionAssetName -Force -ErrorAction stop } } hidden [void] RemoveCCAzureRunAsCertificate() { #remove existing certificate $isCertPresent = Get-AzureRmAutomationCertificate -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -Name $this.certificateAssetName -ErrorAction SilentlyContinue if($isCertPresent) { Remove-AzureRmAutomationCertificate -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -Name $this.certificateAssetName -ErrorAction SilentlyContinue } } hidden [void] UpdateCCAzureRunAsAccount() { $pfxFilePath = $null $thumbPrint = $null try { #fetch existing AD App used in connection $connection = Get-AzureRmAutomationConnection -AutomationAccountName $this.AutomationAccount.Name ` -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.connectionAssetName -ErrorAction Stop $appID = $connection.FieldDefinitionValues.ApplicationId $this.AutomationAccount.AzureADAppName = (Get-AzureRmADApplication -ApplicationId $connection.FieldDefinitionValues.ApplicationId -ErrorAction stop).DisplayName #create new self-signed certificate $selfsignedCertificate = [ActiveDirectoryHelper]::NewSelfSignedCertificate($this.AutomationAccount.AzureADAppName,$this.certificateDetail.CertStartDate,$this.certificateDetail.CertEndDate,$this.certificateDetail.Provider) #create password $secureCertPassword = [ActiveDirectoryHelper]::NewSecurePassword() $pfxFilePath = $env:TEMP+ "\temp.pfx" Export-PfxCertificate -Cert $selfsignedCertificate -Password $secureCertPassword -FilePath $pfxFilePath | Out-Null $publicCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,$selfsignedCertificate.GetRawCertData()) #Authenticating AAD App service principal with newly created certificate credential [ActiveDirectoryHelper]::UpdateADAppCredential($appID,$publicCert,$this.certificateDetail.CredStartDate,$this.certificateDetail.CredEndDate,"False") $thumbPrint = $publicCert.thumbPrint #create certificate asset $newCertificateAsset = $this.NewCCCertificate($pfxFilePath,$secureCertPassword) # Remove existing connection $this.RemoveCCAzureRunAsAccount() # Create a Automation connection asset named AzureRunAsConnection in the Automation account. This connection uses the updated service principal. $newConnectionAsset = $this.NewCCConnection($appID,$thumbPrint) $this.OutputObject.AzureADAppName = $this.AutomationAccount.AzureADAppName $this.OutputObject.AzureRunAsConnection = $newConnectionAsset $this.OutputObject.AzureRunAsCertificate = $newCertificateAsset } finally { #cleanup pfx file Remove-Item -Path $pfxFilePath -Force -ErrorAction SilentlyContinue #cleanup certificate $CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) $CertStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) if($thumbPrint) { $tempCert = $CertStore.Certificates.Find("FindByThumbprint",$thumbPrint,$FALSE) if($tempCert) { $CertStore.Remove($tempCert[0]) } } } } hidden [PSObject] NewCCConnection($appId,$thumbPrint) { $tenantID = (Get-AzureRmSubscription -SubscriptionId $this.Context.SubscriptionId -ErrorAction Stop).TenantId $ConnectionFieldValues = @{"ApplicationId" = $appID; "TenantId" = $tenantID; "CertificateThumbprint" = $thumbPrint; "SubscriptionId" = $this.Context.SubscriptionId} $newConnectionAsset = New-AzureRmAutomationConnection -ResourceGroupName $this.AutomationAccount.ResourceGroup ` -AutomationAccountName $this.AutomationAccount.Name -Name $this.connectionAssetName -ConnectionTypeName AzureServicePrincipal ` -ConnectionFieldValues $ConnectionFieldValues -Description "This connection authenticates runbook with service principal" -ErrorAction stop return $newConnectionAsset } hidden [PSObject] NewCCCertificate($pfxFilePath,$secureCertPassword) { $newCertificateAsset = New-AzureRmAutomationCertificate -ResourceGroupName $this.AutomationAccount.ResourceGroup -AutomationAccountName $this.AutomationAccount.Name ` -Path $pfxFilePath -Name $this.certificateAssetName -Password $secureCertPassword -Exportable -ErrorAction Stop return $newCertificateAsset } #endregion hidden [PSObject] CheckContinuousAssuranceStorage() { #check if tags exist $existingStorage = Find-AzureRmResource -TagName "AzSDKFeature" -TagValue "ContinuousAssuranceStorage" -ExpandProperties| Where-Object {$_.ResourceType -eq "Microsoft.Storage/storageAccounts" -and $_.ResourceGroupName -eq $this.AutomationAccount.ResourceGroup} if(($existingStorage|Measure-Object).Count -eq 0) { #check from name $existingStorage = Find-AzureRmResource -ResourceGroupNameEquals $this.AutomationAccount.ResourceGroup -ResourceNameContains "azsdk" -ResourceType "Microsoft.Storage/storageAccounts" } if(($existingStorage|Measure-Object).Count -gt 1) { throw("Multiple storage accounts found in resource group [$($this.AutomationAccount.ResourceGroup)]. This is not expected."+ " Please backup the required logs/reports from storage. Delete resource group [$($this.AutomationAccount.ResourceGroup)] and install latest version using '"+$this.installCommandName+"' command with required parameters.") } return $existingStorage } } |