Tools/Create-Azure-Sentinel-Solution/V3/createSolutionV3.ps1 (292 lines of code) (raw):

param( [string]$SolutionDataFolderPath = $null ) Write-Host '=======Starting Package Creation using V3 tool=========' if ($null -eq $SolutionDataFolderPath -or $SolutionDataFolderPath -eq '') { $path = Read-Host "Enter solution data folder path " } else { $path = $SolutionDataFolderPath Write-Host "Solution Data folder path specified is : $path" } $defaultPackageVersion = "3.0.0" # for templateSpec this will be 2.0.0 Write-Host "Path $path, DefaultPackageVersion is $defaultPackageVersion" if ($path.length -eq 0) { # path is not provided so check first file from input folder $path = "$PSScriptRoot\input" $inputFile = $(Get-ChildItem $path) if ($inputFile.Count -gt 0) { $inputFile = $inputFile[0] $inputJsonPath = Join-Path -Path $path -ChildPath "$($inputFile.Name)" $contentToImport = Get-Content -Raw $inputJsonPath | Out-String | ConvertFrom-Json # BELOW LINE MAKES USE OF BASEPATH FROM DATA FILE AND ADDS DATA FOLDER NAME. $path = $contentToImport.BasePath + "/Data" } else { Write-Host "Path is not specified and also input folder doesnt have input file. Please make sure to have path specified or add file in side of V3/input folder!" } } $path = $path.Replace('\', '/') $indexOfSolutions = $path.IndexOf('Solutions') if ($indexOfSolutions -le 0) { Write-Host "Please provide data folder path from Solutions folder!" exit 1 } else { $hasDataFolder = $path -like '*/data' if ($hasDataFolder) { # DATA FOLDER PRESENT $dataFolderIndex = $path.LastIndexOf("/data", [StringComparison]"CurrentCultureIgnoreCase") if ($dataFolderIndex -le 0) { Write-Host "Given path is not from Solutions data folders. Please provide data file path from Solution" exit 1 } else { $dataFolderName = $path.Substring($dataFolderIndex + 1) $solutionName = $path.Substring($indexOfSolutions + 10, $dataFolderIndex - ($indexOfSolutions + 10)) $solutionFolderBasePath = $path.Substring(0, $dataFolderIndex) # GET DATA FOLDER FILE NAME $excluded = @("parameters.json", "parameter.json", "system_generated_metadata.json", "testParameters.json") $dataFileName = Get-ChildItem -Path "$solutionFolderBasePath\$dataFolderName\" -recurse -exclude $excluded | ForEach-Object -Process { [System.IO.Path]::GetFileName($_) } if ($dataFileName.Length -le 0) { Write-Host "Data File not present in given folder path!" exit 1 } } } else { Write-Host "Data File not present in given folder path!" exit 1 } } $solutionBasePath = $path.Substring(0, $indexOfSolutions + 10) $repositoryBasePath = $path.Substring(0, $indexOfSolutions) Write-Host "SolutionBasePath is $solutionBasePath, Solution Name $solutionName" $isPipelineRun = $false $commonFunctionsFilePath = $repositoryBasePath + "Tools/Create-Azure-Sentinel-Solution/common/commonFunctions.ps1" $catalogAPIFilePath = $repositoryBasePath + ".script/package-automation/catalogAPI.ps1" $getccpDetailsFilePath = $repositoryBasePath + "Tools/Create-Azure-Sentinel-Solution/common/get-ccp-details.ps1" . $commonFunctionsFilePath # load common functions . $catalogAPIFilePath # load catalog api functions . $getccpDetailsFilePath # load ccp functions try { $ccpDict = @(); $ccpTablesFilePaths = @(); $ccpTablesCounter = 1; $isCCPConnector = $false; foreach ($inputFile in $(Get-ChildItem -Path "$solutionFolderBasePath\$dataFolderName\$dataFileName")) { #$inputJsonPath = Join-Path -Path $path -ChildPath "$($inputFile.Name)" $contentToImport = Get-Content -Raw $inputFile | Out-String | ConvertFrom-Json $has1PConnectorProperty = [bool]($contentToImport.PSobject.Properties.Name -match "Is1PConnector") if ($has1PConnectorProperty) { $Is1PConnectorPropertyValue = [bool]($contentToImport.Is1PConnector) if ($Is1PConnectorPropertyValue) { # when true we terminate package creation. Write-Host "ERROR: Is1PConnector property is deprecated. Please use StaticDataConnector property. Refer link https://github.com/Azure/Azure-Sentinel/blob/master/Tools/Create-Azure-Sentinel-Solution/V3/README.md for more details!" exit 1; } } $basePath = $(if ($solutionBasePath) { $solutionBasePath } else { "https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/" }) $metadataAuthor = $contentToImport.Author.Split(" - "); if ($null -ne $metadataAuthor[1]) { $global:baseMainTemplate.variables | Add-Member -NotePropertyName "email" -NotePropertyValue $($metadataAuthor[1]) $global:baseMainTemplate.variables | Add-Member -NotePropertyName "_email" -NotePropertyValue "[variables('email')]" } $solutionName = $contentToImport.Name #$metadataPath = "$PSScriptRoot/../../../Solutions/$($contentToImport.Name)/$($contentToImport.Metadata)" $metadataPath = $solutionBasePath + "$($contentToImport.Name)/$($contentToImport.Metadata)" $baseMetadata = Get-Content -Raw $metadataPath | Out-String | ConvertFrom-Json if ($null -eq $baseMetadata) { Write-Host "Please verify if the given path is correct and/or Solution folder name and Data file Name attribute value is correct!" exit 1 } #================START: IDENTIFY PACKAGE VERSION============= $solutionOfferId = $baseMetadata.offerId $offerId = "$solutionOfferId" $offerDetails = GetCatalogDetails $offerId $userInputPackageVersion = $contentToImport.version $packageVersion = GetPackageVersion $defaultPackageVersion $offerId $offerDetails $true $userInputPackageVersion if ($packageVersion -ne $contentToImport.version) { $contentToImport.PSObject.Properties.Remove('version') $contentToImport | Add-Member -MemberType NoteProperty -Name 'version' -Value $packageVersion Write-Host "Package version updated to $packageVersion" } $TemplateSpecAttribute = [bool]($contentToImport.PSobject.Properties.Name -match "TemplateSpec") if (!$TemplateSpecAttribute) { $contentToImport | Add-Member -MemberType NoteProperty -Name 'TemplateSpec' -Value $true } $major = $contentToImport.version.split(".")[0] if ($TemplateSpecAttribute -and $contentToImport.TemplateSpec -eq $false -and $major -gt 1) { $contentToImport.PSObject.Properties.Remove('TemplateSpec') $contentToImport | Add-Member -MemberType NoteProperty -Name 'TemplateSpec' -Value $true } #================START: IDENTIFY PACKAGE VERSION============= Write-Host "Package version identified is $packageVersion" if ($major -ge 3) { $global:baseMainTemplate.variables | Add-Member -NotePropertyName "_solutionName" -NotePropertyValue $solutionName $global:baseMainTemplate.variables | Add-Member -NotePropertyName "_solutionVersion" -NotePropertyValue $contentToImport.version } $metadataAuthor = $contentToImport.Author.Split(" - "); $global:solutionId = $baseMetadata.publisherId + "." + $baseMetadata.offerId $global:baseMainTemplate.variables | Add-Member -NotePropertyName "solutionId" -NotePropertyValue "$global:solutionId" $global:baseMainTemplate.variables | Add-Member -NotePropertyName "_solutionId" -NotePropertyValue "[variables('solutionId')]" # VERIFY IF IT IS A CONTENTSPEC OR CONTENTPACKAGE RESOURCE TYPE BY VERIFYING VERSION FROM DATA FILE $contentResourceDetails = returnContentResources($contentToImport.Version) if ($null -eq $contentResourceDetails) { Write-Host "Not able to identify content resource details based on Version. Please verify if Version in data input file is correct!" exit 1; } $dcWithoutSpace = ($baseFolderPath + $solutionName + "/DataConnectors/").Replace("//", "/") $hasDCWithoutSpace = Test-Path -Path $dcWithoutSpace if ($hasDCWithoutSpace) { $DCFolderName = "DataConnectors" } if ($isCCPConnector -eq $false) { $DCFolderName = "Data Connectors" $ccpDict = Get-CCP-Dict -dataFileMetadata $contentToImport -baseFolderPath $solutionBasePath -solutionName $solutionName -DCFolderName $DCFolderName if ($null -ne $ccpDict -and $ccpDict.count -gt 0) { $isCCPConnector = $true [array]$ccpTablesFilePaths = GetCCPTableFilePaths -existingCCPDict $ccpDict -baseFolderPath $solutionBasePath -solutionName $solutionName -DCFolderName $DCFolderName } } Write-Host "isCCPConnector $isCCPConnector" $ccpConnectorCodeExecutionCounter = 1; foreach ($objectProperties in $contentToImport.PsObject.Properties) { if ($objectProperties.Value -is [System.Array] -and $objectProperties.Name.ToLower() -ne 'dependentdomainsolutionids' -and $objectProperties.Name.ToLower() -ne 'staticdataconnectorids') { foreach ($file in $objectProperties.Value) { $file = $file.Replace("$basePath/", "").Replace("Solutions/", "").Replace("$solutionName/", "") $finalPath = ($basePath + $solutionName + "/" + $file).Replace("//", "/") $rawData = $null try { Write-Host "Downloading $finalPath" $rawData = (New-Object System.Net.WebClient).DownloadString($finalPath) } catch { Write-Host "Failed to download $finalPath -- Please ensure that it exists in $([System.Uri]::EscapeUriString($basePath))" -ForegroundColor Red break; } try { $json = ConvertFrom-Json $rawData -ErrorAction Stop; # Determine whether content is JSON or YAML $validJson = $true; } catch { $validJson = $false; } if ($validJson) { # If valid JSON, must be Workbook or Playbook $objectKeyLowercase = $objectProperties.Name.ToLower() if ($objectKeyLowercase -eq "workbooks") { GetWorkbookDataMetadata -file $file -isPipelineRun $isPipelineRun -contentResourceDetails $contentResourceDetails -baseFolderPath $repositoryBasePath -contentToImport $contentToImport } elseif ($objectKeyLowercase -eq "playbooks") { GetPlaybookDataMetadata -file $file -contentToImport $contentToImport -contentResourceDetails $contentResourceDetails -json $json -isPipelineRun $isPipelineRun } elseif ($objectKeyLowercase -eq "data connectors" -or $objectKeyLowercase -eq "dataconnectors") { if ($ccpDict.Count -gt 0) { $isCCPConnectorFile = $false; foreach($item in $ccpDict) { if ($item.DCDefinitionFullPath -eq $finalPath) { $isCCPConnectorFile = $true break; } } if ($isCCPConnectorFile -and $ccpConnectorCodeExecutionCounter -eq 1) { # current file is a ccp connector GetDataConnectorMetadata -file $file -contentResourceDetails $contentResourceDetails -dataFileMetadata $contentToImport -solutionFileMetadata $baseMetadata -dcFolderName $DCFolderName -ccpDict $ccpDict -solutionBasePath $basePath -solutionName $solutionName -ccpTables $ccpTablesFilePaths -ccpTablesCounter $ccpTablesCounter $ccpConnectorCodeExecutionCounter += 1 } elseif ($isCCPConnectorFile -and $ccpConnectorCodeExecutionCounter -gt 1) { continue; } else { # current file is a normal connector GetDataConnectorMetadata -file $file -contentResourceDetails $contentResourceDetails -dataFileMetadata $contentToImport -solutionFileMetadata $baseMetadata -dcFolderName $DCFolderName -ccpDict $null -solutionBasePath $basePath -solutionName $solutionName -ccpTables $null -ccpTablesCounter $ccpTablesCounter } } else { # current file is a normal connector GetDataConnectorMetadata -file $file -contentResourceDetails $contentResourceDetails -dataFileMetadata $contentToImport -solutionFileMetadata $baseMetadata -dcFolderName $DCFolderName -ccpDict $null -solutionBasePath $basePath -solutionName $solutionName -ccpTables $null -ccpTablesCounter $ccpTablesCounter } } elseif ($objectKeyLowercase -eq "savedsearches") { GenerateSavedSearches -json $json -contentResourceDetails $contentResourceDetails } elseif ($objectKeyLowercase -eq "watchlists") { $watchListFileName = Get-ChildItem $finalPath GenerateWatchList -json $json -isPipelineRun $isPipelineRun -watchListFileName $watchListFileName.BaseName } } else { if ($file -match "(\.yaml)$" -and $objectProperties.Name.ToLower() -ne "parsers") { $objectKeyLowercase = $objectProperties.Name.ToLower() if ($objectKeyLowercase -eq "hunting queries") { GetHuntingDataMetadata -file $file -rawData $rawData -contentResourceDetails $contentResourceDetails } else { GenerateAlertRule -file $file -contentResourceDetails $contentResourceDetails } } else { GenerateParsersList -file $file -contentToImport $contentToImport -contentResourceDetails $contentResourceDetails } } } } elseif ($objectProperties.Name.ToLower() -eq "metadata") { try { $finalPath = $metadataPath $rawData = $null try { Write-Host "Downloading $finalPath" $rawData = (New-Object System.Net.WebClient).DownloadString($finalPath) } catch { Write-Host "Failed to download $finalPath -- Please ensure that it exists in $([System.Uri]::EscapeUriString($basePath))" -ForegroundColor Red break; } try { $json = ConvertFrom-Json $rawData -ErrorAction Stop; # Determine whether content is JSON or YAML $validJson = $true; } catch { $validJson = $false; } if ($validJson -and $json) { PrepareSolutionMetadata -solutionMetadataRawContent $json -contentResourceDetails $contentResourceDetails -defaultPackageVersion $defaultPackageVersion } else { Write-Host "Failed to load Metadata file $file -- Please ensure that it exists in $([System.Uri]::EscapeUriString($basePath))" -ForegroundColor Red } } catch { Write-Host "Failed to load Metadata file $file -- Please ensure that the SolutionMetadata file exists in $([System.Uri]::EscapeUriString($basePath))" -ForegroundColor Red break; } } } $global:analyticRuleCounter -= 1 $global:workbookCounter -= 1 $global:playbookCounter -= 1 $global:connectorCounter -= 1 $global:parserCounter -= 1 $global:huntingQueryCounter -= 1 $global:watchlistCounter -= 1 updateDescriptionCount $global:connectorCounter "**Data Connectors:** " "{{DataConnectorCount}}" $(checkResourceCounts $global:parserCounter, $global:analyticRuleCounter, $global:workbookCounter, $global:playbookCounter, $global:huntingQueryCounter, $global:watchlistCounter) updateDescriptionCount $global:parserCounter "**Parsers:** " "{{ParserCount}}" $(checkResourceCounts $global:analyticRuleCounter, $global:workbookCounter, $global:playbookCounter, $global:huntingQueryCounter, $global:watchlistCounter) updateDescriptionCount $global:workbookCounter "**Workbooks:** " "{{WorkbookCount}}" $(checkResourceCounts $global:analyticRuleCounter, $global:playbookCounter, $global:huntingQueryCounter, $global:watchlistCounter) updateDescriptionCount $global:analyticRuleCounter "**Analytic Rules:** " "{{AnalyticRuleCount}}" $(checkResourceCounts $global:playbookCounter, $global:huntingQueryCounter, $global:watchlistCounter) updateDescriptionCount $global:huntingQueryCounter "**Hunting Queries:** " "{{HuntingQueryCount}}" $(checkResourceCounts $global:playbookCounter, $global:watchlistCounter) updateDescriptionCount $global:watchlistCounter "**Watchlists:** " "{{WatchlistCount}}" $(checkResourceCounts @($global:playbookCounter)) updateDescriptionCount $global:customConnectorsList.Count "**Custom Azure Logic Apps Connectors:** " "{{LogicAppCustomConnectorCount}}" $(checkResourceCounts @($global:playbookCounter)) updateDescriptionCount $global:functionAppList.Count "**Function Apps:** " "{{FunctionAppsCount}}" $(checkResourceCounts @($global:playbookCounter)) updateDescriptionCount ($global:playbookCounter - $global:customConnectorsList.Count - $global:functionAppList.Count) "**Playbooks:** " "{{PlaybookCount}}" $false GeneratePackage -solutionName $solutionName -contentToImport $contentToImport -calculatedBuildPipelinePackageVersion $contentToImport.Version; RunArmTtkOnPackage -solutionName $solutionName -isPipelineRun $false; # check if mainTemplate and createUiDefinition json files are valid or not CheckJsonIsValid($solutionFolderBasePath) } } catch { Write-Host "Error occurred in catch of createSolutionV3 file Error details are $_" }