Skip to content

Commit

Permalink
Merge branch 'master' into v-mchatla/Rapid7InsightVM-UpdatingLastSave…
Browse files Browse the repository at this point in the history
…dTime
  • Loading branch information
v-mchatla committed Jun 29, 2023
2 parents f2dfe38 + 686069d commit f09e261
Show file tree
Hide file tree
Showing 1,174 changed files with 111,020 additions and 30,115 deletions.
14 changes: 12 additions & 2 deletions .script/SolutionValidations/solutionValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import * as logger from "./../utils/logger";
import { ExitCode } from "./../utils/exitCode";
import { IsValidSolutionDomainsVerticals } from "./validDomainsVerticals";
import { IsValidSupportObject } from "./validSupportObject";
import { MainTemplateDomainVerticalValidationError, MainTemplateSupportObjectValidationError } from "../utils/validationError";
import { IsValidBrandingContent } from "./validMSBranding";
import { IsValidSolutionID } from "./validSolutionID";
import { MainTemplateDomainVerticalValidationError, MainTemplateSupportObjectValidationError, InvalidFileContentError, InvalidSolutionIDValidationError } from "../utils/validationError";



// function to check if the solution is valid
export async function IsValidSolution(filePath: string): Promise<ExitCode> {
IsValidSolutionDomainsVerticals(filePath);
IsValidSupportObject(filePath);
IsValidBrandingContent(filePath);
IsValidSolutionID(filePath);
return ExitCode.SUCCESS;
}

Expand All @@ -32,12 +36,18 @@ let CheckOptions = {
},
// Callback function to handle errors during execution
onExecError: async (e: any, filePath: string) => {
console.log(`Solution Validation Failed. File path: ${filePath}. Error message: ${e.message}`);
console.log(`Solution Validation Failed. File path: ${filePath} \nError message: ${e.message}`);
if (e instanceof MainTemplateDomainVerticalValidationError) {
logger.logError("Please refer link https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/sentinel/sentinel-solutions.md?msclkid=9a240b52b11411ec99ae6736bd089c4a#categories-for-microsoft-sentinel-out-of-the-box-content-and-solutions for valid Domains and Verticals.");
} else if (e instanceof MainTemplateSupportObjectValidationError) {
logger.logError("Validation for Support object failed in Main Template.");
}
else if (e instanceof InvalidFileContentError) {
logger.logError("Validation for Microsoft Sentinel Branding Failed.");
}
else if (e instanceof InvalidSolutionIDValidationError) {
logger.logError("Validation for Solution ID Failed.");
}
},
// Callback function to handle final failure
onFinalFailed: async () => {
Expand Down
5 changes: 3 additions & 2 deletions .script/SolutionValidations/validDomainsVerticals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ export function IsValidSolutionDomainsVerticals(filePath: string): ExitCode {
let resources = jsonFile.resources;

// filter resources that have type "Microsoft.OperationalInsights/workspaces/providers/metadata"
const filteredResource = resources.filter(function (resource: { type: string; }) {
return resource.type === "Microsoft.OperationalInsights/workspaces/providers/metadata";
const filteredResource = resources.filter(function (resource: { type: string }) {
return resource.type === "Microsoft.OperationalInsights/workspaces/providers/metadata" ||
resource.type === "Microsoft.OperationalInsights/workspaces/providers/contentPackages";
});
if (filteredResource.length > 0) {
filteredResource.forEach((element: { hasOwnProperty: (arg0: string) => boolean; properties: { hasOwnProperty: (arg0: string) => boolean; categories: any; }; }) => {
Expand Down
70 changes: 70 additions & 0 deletions .script/SolutionValidations/validMSBranding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { InvalidFileContentError } from "./../utils/validationError";
import { ExitCode } from "../utils/exitCode";
import fs from "fs";

type AttributeConfig = {
mainTemplateAttributes: string[];
createUIDefinitionAttributes: string[];
};

const attributeConfig: AttributeConfig = {
mainTemplateAttributes: ["descriptionMarkdown", "description"],
createUIDefinitionAttributes: ["text", "description"],
};

export function IsValidBrandingContent(filePath: string): ExitCode {
// Skip validation if file path contains "SentinelOne"
if (filePath.includes("SentinelOne")) {
return ExitCode.SUCCESS;
}

const errors: string[] = [];

// check if the file is mainTemplate.json or createUiDefinition.json
if (filePath.endsWith("mainTemplate.json")) {
validateFileContent(filePath, attributeConfig.mainTemplateAttributes, errors);
} else if (filePath.endsWith("createUiDefinition.json")) {
validateFileContent(filePath, attributeConfig.createUIDefinitionAttributes, errors);
} else {
console.warn(`Could not identify JSON file as mainTemplate.json or createUiDefinition.json. Skipping. File path: ${filePath}`);
}

// Throw a single error with all the error messages concatenated
if (errors.length > 0) {
throw new InvalidFileContentError(errors.join("\n"));
}

// Return success code after completion of the check
return ExitCode.SUCCESS;
}

function validateFileContent(filePath: string, attributeNames: string[], errors: string[]): void {
const fileContent = fs.readFileSync(filePath, "utf8");
const jsonContent = JSON.parse(fileContent);

traverseAttributes(jsonContent, attributeNames, errors);
}

function traverseAttributes(jsonContent: any, attributeNames: string[], errors: string[]): void {
for (const key in jsonContent) {
if (jsonContent.hasOwnProperty(key)) {
const attributeValue = jsonContent[key];
if (attributeNames.includes(key) && typeof attributeValue === "string") {
validateAttribute(attributeValue, key, errors);
}
if (typeof attributeValue === "object" && attributeValue !== null) {
traverseAttributes(attributeValue, attributeNames, errors);
}
}
}
}

function validateAttribute(attributeValue: string, attributeName: string, errors: string[]): void {
const sentinelRegex = /(?<!Microsoft\s)(?<!\S)Sentinel(?!\S)/g;
const updatedValue = attributeValue.replace(sentinelRegex, "Microsoft Sentinel");

if (attributeValue !== updatedValue) {
const error = `Inaccurate product branding used in '${attributeName}' for '${attributeValue}'. Use "Microsoft Sentinel" instead of "Sentinel"`;
errors.push(error);
}
}
42 changes: 42 additions & 0 deletions .script/SolutionValidations/validSolutionID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { InvalidSolutionIDValidationError } from "./../utils/validationError";
import { ExitCode } from "../utils/exitCode";
import fs from "fs";

export function IsValidSolutionID(filePath: string): ExitCode {
// Check if the file path ends with mainTemplate.json
if (!filePath.endsWith("mainTemplate.json")) {
return ExitCode.SUCCESS;
}

// Parse the JSON content from the file
const jsonContent = JSON.parse(fs.readFileSync(filePath, "utf8"));

// Get the 'variables' section from the JSON
const variables = jsonContent.variables;

// Check if 'solutionId' attribute is present
if (variables && "solutionId" in variables) {
const solutionId = variables.solutionId;

// Validate if the solution ID is empty
if (!solutionId) {
throw new InvalidSolutionIDValidationError(`Empty solution ID. Expected format: publisherID.offerID. and it must be in lowercase. Found empty value.`);
}

// Validate the solution ID format
const regex = /^[^.]+\.[^.]+$/;
if (!regex.test(solutionId)) {
throw new InvalidSolutionIDValidationError(`Invalid solution ID format. Expected format: publisherID.offerID. and it must be in lowercase. Found: ${solutionId}`);
}

// Validate the solution ID case (lowercase)
if (solutionId !== solutionId.toLowerCase()) {
throw new InvalidSolutionIDValidationError(`Invalid solution ID format. Expected format: publisherID.offerID. and it must be in lowercase. Found: ${solutionId}`);
}
} else {
throw new InvalidSolutionIDValidationError(`Missing 'solutionId' attribute in the file. File path: ${filePath}`);
}

// Return success code after completion of the check
return ExitCode.SUCCESS;
}
5 changes: 3 additions & 2 deletions .script/SolutionValidations/validSupportObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export function IsValidSupportObject(filePath: string): ExitCode {
let resources = jsonFile.resources;

// filter resources that have type "Microsoft.OperationalInsights/workspaces/providers/metadata"
const filteredResource = resources.filter(function (resource: { type: string; }) {
return resource.type === "Microsoft.OperationalInsights/workspaces/providers/metadata";
const filteredResource = resources.filter(function (resource: { type: string }) {
return resource.type === "Microsoft.OperationalInsights/workspaces/providers/metadata" ||
resource.type === "Microsoft.OperationalInsights/workspaces/providers/contentPackages";
});

if (filteredResource.length > 0) {
Expand Down
190 changes: 190 additions & 0 deletions .script/package-automation/catelogAPI.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
function GetCatelogDetails($offerId)
{
if ($null -eq $offerId -or $offerId -eq '')
{
Write-Host "Provided OfferId for CatelogAPI details is blank! Please provide valid OfferId!";
return $null;
}
else {
try {
$formatUri = 'https://catalogapi.azure.com/offers?api-version=2018-08-01-beta&$filter=(categoryIds/any(cat:+cat+eq+''AzureSentinelSolution'')+or+keywords/any(key:+contains(key,''f1de974b-f438-4719-b423-8bf704ba2aef'')))+and+(offerId+eq+%27'+ $offerId +'%27)'
$response = Invoke-RestMethod -Uri $formatUri -Method 'GET' -Headers $headers -Body $body

$offerDetails = $response.items | Where-Object offerId -eq $offerId

if ($null -eq $offerDetails)
{
# when not found by offerId then use planId
$offerDetails = $response.items | Where-Object planId -eq $offerId
}

if ($null -eq $offerDetails)
{
# DETAILS NOT FOUND
Write-Host "CatelogAPI Details not found for offerId $offerId"
return $null;
}
else {
Write-Host "CatelogAPI Details found for offerId $offerId"
return $offerDetails;
}
}
catch {
Write-Host "Error occured in CatelogAPI. Error Details : $_";
return $null;
}
}
}

function CompareVersionStrings([string]$Version1, [string]$Version2) {

$v1 = $Version1.Split('.') -replace '^0', '0.'
$v2 = $Version2.Split('.') -replace '^0', '0.'

[Array]::Resize( [ref] $v1, 4 )
[Array]::Resize( [ref] $v2, 4 )

for ($i=0; $i-lt 4; $i++) {
switch (($v1[$i].length).CompareTo(($v2[$i].length))) {
{$_ -lt 0} { $v1[$i] = $v1[$i].PadRight($v2[$i].Length,'0') }
{$_ -gt 0} { $v2[$i] = $v2[$i].PadRight($v1[$i].Length,'0') }
}
}

$v1f = $v1 | % {[float]$_}
$v2f = $v2 | % {[float]$_}

return [Collections.StructuralComparisons]::StructuralComparer.Compare( $v1f, $v2f )
}

function GetNewVersion($packageVersionAttribute, $dataFileContentObject, $defaultPackageVersion, $templateSpecAttribute, $isNewSolution)
{
$templateSpecDefaultVersion = '2.0.0'
if($packageVersionAttribute)
{
if ($null -eq $dataFileContentObject.Version -or $dataFileContentObject.Version -eq '')
{
return $templateSpecAttribute ? ($templateSpecDefaultVersion, $true) : ($defaultPackageVersion, $true)
}
else
{
return ($dataFileContentObject.Version, $false)
}
}
else
{
return $templateSpecAttribute ? ($templateSpecDefaultVersion, $true) : ($defaultPackageVersion, $true)
}
}

function GetIncrementedVersion($version)
{
$major,$minor,$build,$revision = $version.split(".")
$newBuildVersion = [int]$build + 1
return "$major.$minor.$newBuildVersion"
}

function GetOfferVersion($offerId, $mainTemplateUrl)
{
if ($null -eq $mainTemplateUrl -or $mainTemplateUrl -eq '')
{
Write-Host "Provided MainTemplateUrl for GetOfferVersion details is blank! Please provide valid MainTemplateUrl!";
return $null;
}
else
{
try
{
$response = Invoke-RestMethod -Uri $mainTemplateUrl -Method 'GET' -Headers $headers
$metadataDetails = $response.resources | Where-Object { ($_.name -eq "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]" -or $_.name -eq "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_sourceId'))]") -and ($_.type -eq "Microsoft.OperationalInsights/workspaces/providers/metadata")};

if ($null -eq $metadataDetails -or $metadataDetails -eq '')
{
Write-Host "Offer Metadata Version details in MainTemplate is not found!"
return $null;
}
else
{
Write-Host "Offer Metadata Version details in MainTemplate is found!"
return $metadataDetails.properties.version;
}
}
catch
{
Write-Host "Error occured in CatelogAPI. Error Details11 : $_";
return $null;
}
}
}

function GetPackageVersion($defaultPackageVersion, $offerId, $offerDetails, $packageVersionAttribute, $userInputPackageVersion)
{
if ($packageVersionAttribute)
{
$userInputMajor,$userInputMinor,$userInputBuild,$userInputRevision = $userInputPackageVersion.split(".")
$defaultMajor,$defaultMinor,$defaultBuild,$defaultRevision = $defaultPackageVersion.split(".")

if ($userInputMajor -ge '2' -and $userInputMinor -gt $defaultMinor)
{
#return as is value of package version as middle value is greater
return $userInputPackageVersion
}
}

$defaultVersionMessage = "Package Version set to Default version $defaultPackageVersion"
if ($null -eq $offerDetails)
{
Write-Host "CatalogAPI Offer details not found for given offerId $offerId. $defaultVersionMessage"
return $defaultPackageVersion
}
else
{
$mainTemplateDetails = $offerDetails.plans.artifacts | Where-Object {$_.type -eq "Template" -and $_.name -eq "DefaultTemplate"}

if ($null -eq $mainTemplateDetails)
{
# WE WILL TAKE WHATEVER VERSION IS SPECIFIED IN THE DATA INPUT FILE WITHOUT INCREMENTING IT
Write-Host "CatalogAPI mainTemplate details not found for given offerId $offerId. $defaultVersionMessage"
return $defaultPackageVersion
}
else
{
# CHECK IF CATELOG API HAS DETAILS AND IDENTIFY THE VERSION
$mainTemplateUri = $mainTemplateDetails.uri

if ($null -eq $mainTemplateUri)
{
Write-Host "CatalogAPI mainTemplate details missing URI for given offerId $offerId. $defaultVersionMessage"
return $defaultPackageVersion
}
else
{
# OFFER DETAILS FOUND SO IDENTIFY THE VERSION IN MAINTEMPLATE FILE
$offerMetadataVersion = GetOfferVersion $offerId $mainTemplateUri
if ($null -eq $offerMetadataVersion -or $offerMetadataVersion -eq '')
{
Write-Host "CatalogAPI mainTemplate details URI file is missing version or version is blank so $defaultVersionMessage"
return $defaultPackageVersion
}
else
{
$identifiedOfferVersion = $offerMetadataVersion
$catelogMajor,$catelogminor,$catelogbuild,$catelogrevision = $identifiedOfferVersion.split(".")
$defaultMajor,$defaultminor,$defaultbuild,$defaultrevision = $defaultPackageVersion.split(".")

if ($defaultMajor -gt $catelogMajor)
{
# eg: 3.0.0 > 2.0.1 ==> 3.0.0
Write-Host "Default Package version is greater then the CatalogAPI version so $defaultVersionMessage"
return $defaultPackageVersion
}
else
{
$packageVersion = GetIncrementedVersion $identifiedOfferVersion
return $packageVersion
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@
{
"Name": "Process",
"Type": "string"
},
{
"Name": "Username",
"Type": "string"
}
]
}
Loading

0 comments on commit f09e261

Please sign in to comment.