diff --git a/Documentation/Wiki/Frontends/MSGuardian-SDK/baselines-and-suppressions.md b/Documentation/Wiki/Frontends/MSGuardian-SDK/baselines-and-suppressions.md index 09e2f38c4..9aea190fb 100644 --- a/Documentation/Wiki/Frontends/MSGuardian-SDK/baselines-and-suppressions.md +++ b/Documentation/Wiki/Frontends/MSGuardian-SDK/baselines-and-suppressions.md @@ -1,4 +1,12 @@ # Generating Baselines and Suppression files for BuildXL builds +## Automatic/Cloudbuild generation +To automatically generate baselines or suppressions pass `/p:[Tool.Guardian]genBaseline=1` for baselines or `/p:[Tool.Guardian]genSuppressions=1` for suppressions (but not both at once) as a command line argument to BuildXL. + +Set `autoGeneratedBaselineSuppressionLocation` in the SDK arguments to the directory where the generated baselines/suppressions should be written. Then, depending on which of the above flags were passed in, set `baselineFileName` or `suppressionFileName` for the name of each baseline/suppression file to be generated for the given call to the Guardian SDK. + +Once the build completes, check the directory specified in `autoGeneratedBaselineSuppressionLocation` for the newly generated baselines/suppressions. + +## Manual local only generation The `guardian baseline` and `guardian suppress` commands can be used to baseline and suppress guardian violations. These must be run manually and committed to the repository. 1. Run BuildXL to get a failing build with Guardian violations. diff --git a/Documentation/Wiki/Frontends/MSGuardian-SDK/compliancebuild.md b/Documentation/Wiki/Frontends/MSGuardian-SDK/compliancebuild.md new file mode 100644 index 000000000..17babb793 --- /dev/null +++ b/Documentation/Wiki/Frontends/MSGuardian-SDK/compliancebuild.md @@ -0,0 +1,21 @@ +# Compliance Build on Cloudbuild +The compliance build on Cloudbuild will run various Guardian tools against the repository root on CB builds to ensure that the repository complies with various Microsoft security standards. Currently it is opt in while repositories are onboarded, but it will eventually be made mandatory for all builds. In its current state, the compliance build will run CredScan against the repo root as soon as it is checked out on Cloudbuild. + +## Enabling Compliance Build +To enable (or disable) compliance build, use the `EnableGuardianBuild` flag in your Cloudbuild configuration when submitting a new build. Additionally, the `GuardianBuildFlags` argument (optional) can be added to pass flags into the Guardian SDK. + +``` +{ + 'SpecificRunnerOptions' : { + 'EnableGuardianBuild' : true, + 'GuardianBuildFlags' : '/p:[Tool.Guardian]genBaseline=1', + }, +} +``` + +## Generating Baselines/Suppressions on Cloudbuild +Suppressions and baselines can be automatically generated on Cloudbuild by setting `/p:[Tool.Guardian]genBaseline=1` for baselines or `/p:[Tool.Guardian]genSuppressions=1` for suppressions (but not both at once) inside the `GuardianBuildFlags` argument in the configuration for the build. This will cause the Compliance build to pass by not allowing the build to break on Guardian errors, and will generate a set of baselines/suppressions under the `{ComplianceBuildLogDirectory}/Guardian` directory. These can be retrieved using the Cloudbuild web interface or with the log drop for the build. + +Once generated, manually copy these files to the `{RepositoryRoot}/.config/buildxl/compliance` directory, and check them into the repository. Finally, rebuild without the genBaseline or genSuppressions flags and ensure that the Compliance build passes with the newly generated baselines or suppressions. + +Note: The flags above should *only* be used temporarily to generate baselines for a single build. They should not be set in the queue configuration for a repository. \ No newline at end of file diff --git a/Public/Sdk/Public/Json/deployment.dsc b/Public/Sdk/Public/Json/deployment.dsc new file mode 100644 index 000000000..f6a1fb35d --- /dev/null +++ b/Public/Sdk/Public/Json/deployment.dsc @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +import * as Deployment from "Sdk.Deployment"; +@@public +export const deployment : Deployment.Definition = { + contents: [ + f`jsonSdk.dsc`, + f`module.config.dsc` + ] +}; \ No newline at end of file diff --git a/Public/Sdk/Public/Tools/Guardian/LiteralFiles/module.config.dsc.literal b/Public/Sdk/Public/Tools/Guardian/LiteralFiles/module.config.dsc.literal new file mode 100644 index 000000000..d47cd88b9 --- /dev/null +++ b/Public/Sdk/Public/Tools/Guardian/LiteralFiles/module.config.dsc.literal @@ -0,0 +1,4 @@ +module({ + name: "Sdk.Guardian", + projects: [ p`Tool.Guardian.dsc`, p`Tool.Guardian.CredScan.dsc` ] +}); diff --git a/Public/Sdk/Public/Tools/Guardian/LiteralFiles/package.config.dsc.literal b/Public/Sdk/Public/Tools/Guardian/LiteralFiles/package.config.dsc.literal deleted file mode 100644 index ccb9cad69..000000000 --- a/Public/Sdk/Public/Tools/Guardian/LiteralFiles/package.config.dsc.literal +++ /dev/null @@ -1,4 +0,0 @@ -module({ - name: "Sdk.Guardian", - projects: [ p`Tool.Guardian.dsc` ] -}); diff --git a/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.CredScan.dsc b/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.CredScan.dsc new file mode 100644 index 000000000..2054d0f06 --- /dev/null +++ b/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.CredScan.dsc @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import {Transformer} from "Sdk.Transformers"; + +const autoGenerateBaselines = Environment.getFlag(guardianGenerateBaselines); +const autoGenerateSuppressions = !autoGenerateBaselines && Environment.getFlag(guardianGenerateSuppressions); +const complianceBaselineSuppressionLocation = Environment.hasVariable("BUILDXL_ENLISTMENT_ROOT") ? d`${Environment.getPathValue("BUILDXL_ENLISTMENT_ROOT")}/.config/buildxl/compliance` : undefined; +const guardianConfigFile = f`${Context.getMount("SourceRoot").path}/guardianBuildConfig.gdnconfig`; + +// Compliance build specific Environment variables +const directoriesNamesToIgnore = "[Tool.Guardian]complianceIgnoreDirectories"; +const filesPerCredScanCall = "[Tool.Guardian]complianceFilesPerCredScanCall"; + +/** + * Calling this function will create Guardian pips with CredScan only on the entire repository from the guardianBuildRoot directory. + * + * When running on Cloudbuild, it is not necessary to provide a Guardian install location. Instead the Guardian binaries will + * be from acquired from the Guardian drop. + */ +@@public +export function runCredScanOnEntireRepository(guardianToolRoot : StaticDirectory, guardianBuildRoot : Directory) : Transformer.ExecuteResult[] { + if (!Environment.hasVariable("TOOLPATH_GUARDIAN")) { + Contract.fail("Guardian drop root must be provided with the 'TOOLPATH_GUARDIAN' environment variable."); + } + + return addGuardianPerDirectoryForRepository(guardianBuildRoot, guardianToolRoot, d`${Environment.getPathValue("TOOLPATH_GUARDIAN")}`); +} + +/** + * Goes through each directory under the given root directory and creates CredScan calls per ~500 files. + */ +function addGuardianPerDirectoryForRepository(rootDirectory : Directory, guardianToolRoot : StaticDirectory, guardianDrop : Directory) : Transformer.ExecuteResult[] { + let results : Transformer.ExecuteResult[] = []; + let files : File[] = glob(rootDirectory, "*"); + let directories = globFolders(rootDirectory, "*", /*recursive*/false); + let directoryIndex = 0; + + // These are directories that are local to a given repository that are not checked in remotely + const directoryAtomsToIgnore = Set.create( + // Defaults + a`.git`, + a`.cloudbuild`, + a`.corext`, + a`Out`, + a`node_modules`, + // User specified + ...addIfLazy(Environment.hasVariable(directoriesNamesToIgnore), () => { + const directoryList = Environment.getStringValue(directoriesNamesToIgnore).split(","); + + return directoryList.map(dir => a`${dir}`); + }) + ); + const directoryPathsToIgnore = Set.create( + d`${Environment.getPathValue("BUILDXL_ENLISTMENT_ROOT")}/common/temp` // well known path for rush install (not part of initially checked out sources) + ); + + const minFilesPerCall = Environment.hasVariable(filesPerCredScanCall) ? Environment.getNumberValue(filesPerCredScanCall) : 500; + + while (directoryIndex < directories.length) { + if (directoryAtomsToIgnore.contains(directories[directoryIndex].name) || directoryPathsToIgnore.contains(directories[directoryIndex])) { + directoryIndex++; + continue; + } + + files = files.concat(glob(directories[directoryIndex], "*")); + directories = directories.concat(globFolders(directories[directoryIndex], "*", /*recursive*/false)); + + if (files.length >= minFilesPerCall || (directoryIndex === directories.length - 1 && files.length > 0)) { + results.push(createGuardianCall(guardianToolRoot, guardianDrop, files, directoryIndex)); + files = []; + } + + directoryIndex++; + } + + return results; +} + +/** + * Generates a Guardian call for the CredScan compliance build. + * Baselines/Suppressions will be picked up from {SourceRoot}/.config/buildxl/compliance automatically. + * LogLevel is set to Warning in Guardian. + */ +function createGuardianCall(guardianToolRoot : StaticDirectory, guardianDrop : Directory, files : File[], directoryIndex : number) : Transformer.ExecuteResult { + const exportDir = Context.getNewOutputDirectory("credScan"); + const baselines = glob(complianceBaselineSuppressionLocation, "*.gdnbaselines"); + const suppressions = glob(complianceBaselineSuppressionLocation, "*.gdnsuppress"); + + // Generate a TSV file for all files to be scanned + const tsvFile = Transformer.writeAllLines(p`${exportDir.path}/guardian.TSV`, files.map(file => file.path)); + + const guardianArgs : GuardianArguments = { + guardianToolRootDirectory: guardianToolRoot, + guardianConfigFile: guardianConfigFile, // Pick up default config from drop directory + guardianResultFile: f`${exportDir.path}/credScanResult.sarif`, + guardianPackageDirectory: d`${guardianDrop}/packages`, + guardianToolWorkingDirectory: exportDir, // Set this to pick up the newly generated tsv file automatically + filesToBeScanned: files, + additionalDependencies: [tsvFile], + logLevel: "Warning", // Display only warnings and errors only to simplify debugging and reduce log file size + baselineFiles: baselines.length > 0 ? baselines : undefined, + suppressionFiles: suppressions.length > 0 ? suppressions : undefined, + autoGeneratedBaselineSuppressionLocation: autoGenerateBaselines || autoGenerateSuppressions + ? d`${Context.getMount("LogsDirectory").path}/Guardian` + : undefined, + baselineFileName: autoGenerateBaselines ? a`${directoryIndex.toString()}.gdnbaselines` : undefined, + suppressionFileName: autoGenerateSuppressions ? a`${directoryIndex.toString()}.gdnsuppressions` : undefined, + }; + + return runGuardian(guardianArgs, /*skipInstall*/true); +} \ No newline at end of file diff --git a/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.dsc b/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.dsc index eaaaa50fc..44ca02be1 100644 --- a/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.dsc +++ b/Public/Sdk/Public/Tools/Guardian/Tool.Guardian.dsc @@ -7,15 +7,26 @@ import * as Json from "Sdk.Json"; @@public export const guardianTag = "msguardian"; -const guardianDisableFlag = "[Tool.Guardian]disable"; -const guardianWarnOnlyFlag = "[Tool.Guardian]warnOnly"; +/** Environment Variables */ +export const guardianDisableFlag = "[Tool.Guardian]disable"; +export const guardianWarnOnlyFlag = "[Tool.Guardian]warnOnly"; +export const guardianBaselineSuppressionDirectory = "[Tool.Guardian]baselineSuppressDir"; +export const guardianGenerateBaselines = "[Tool.Guardian]genBaseline"; +export const guardianGenerateSuppressions = "[Tool.Guardian]genSuppressions"; + const guardianInstallMutex = "BuildXL.Tools.Guardian.Install.Phase"; const guardianExecutableName : PathAtom = PathAtom.create("guardian.cmd"); const defaultGuardianToolWorkingDirectory = d`${Context.getMount("SourceRoot").path}`; + +const cloudbuildToolPath : Directory = Environment.hasVariable("TOOLPATH_GUARDIAN") ? d`${Environment.getDirectoryValue("TOOLPATH_GUARDIAN")}` : undefined; +const cloudBuildDotNetDirectory = cloudbuildToolPath ? d`${cloudbuildToolPath}/dotnetX64` : undefined; +const cloudBuildTempDirectory = cloudbuildToolPath ? d`${cloudbuildToolPath.parent.parent.path}/temp` : undefined; + const guardianUntrackedDirectories = addIfLazy(Context.getCurrentHost().os === "win", () => [ // Accessed by the Guardian CLI d`${Context.getMount("ProgramFilesX86").path}/dotnet`, d`${Context.getMount("ProgramFiles").path}/dotnet`, + ...addIf(cloudBuildDotNetDirectory !== undefined, cloudBuildDotNetDirectory), d`${Context.getMount("ProgramData").path}/Microsoft/NetFramework`, d`${Context.getMount("ProgramData").path}/Microsoft/VisualStudio/Setup`, // Config files accessed by nuget during Guardian install phase @@ -28,20 +39,31 @@ const guardianUntrackedDirectories = addIfLazy(Context.getCurrentHost().os === " // Nuget will cache packages under ~/.nuget/packages, however they will always get copied over to the package install directory // Since we track the package install directory, we will untrack the ~/.nuget/packages directory. d`${Context.getMount("UserProfile").path}/.nuget/packages`, + // Guardian occasionally does not honour the TMP/TEMP variables, and instead will write temp files to other locations listed below + // The temp directory on cloudbuild will always be created under X:\a\b\temp, when the cloudbuild drop is at X:\a\b\c\d (this location is not expected to change between builds) + // TODO: Remove this line once the Guardian team fixes this bug to use the temp directory specified in TEMP/TMP instead. + ...addIf(cloudBuildTempDirectory !== undefined, cloudBuildTempDirectory), ]); /** * Tool definition for guardian * Note: Package root is untracked because we rely on the .gdn/c directory to know when a new Guardian tool is available. */ -function getGuardianTool(guardianRoot : StaticDirectory, guardianPaths : GuardianPaths) : Transformer.ToolDefinition { +function getGuardianTool(args: GuardianArguments, guardianPaths : GuardianPaths) : Transformer.ToolDefinition { return { - exe: findGuardianExecutable(guardianRoot), + exe: findGuardianExecutable(args.guardianToolRootDirectory), description: "Microsoft Guardian", dependsOnWindowsDirectories: true, dependsOnAppDataDirectory: true, prepareTempDirectory: true, - untrackedDirectoryScopes: [...guardianUntrackedDirectories, d`${guardianPaths.install}`], + untrackedDirectoryScopes: [ + ...guardianUntrackedDirectories, + d`${guardianPaths.install}`, + // When using the integrated Guardian package (such as the one on CloudBuild), Guardian may write under these paths even though it has a temporary directory prepared. + d`${args.guardianToolRootDirectory.path}/temp`, + d`${args.guardianToolRootDirectory.path}/tmp`, + ...addIf(args.autoGeneratedBaselineSuppressionLocation !== undefined, args.autoGeneratedBaselineSuppressionLocation), + ], // Untracking localRepo/.gdnhistory to ignore double writes between guardian init and guardian run // the history in here is not important because each Guardian call gets it's own .gdnhistory file. // globalRepo/.gdnhistory will also be read from, but it will not be written to. @@ -64,7 +86,7 @@ function findGuardianExecutable(guardianRoot : StaticDirectory) : File { } /** - * Schedules a guardian pip with the specified arguments. + * Schedules a guardian pip with the specified arguments. The install step can be skipped when a package cache is available using the skipInstall option. * * Guardian is currently only supported on Windows. * @@ -82,7 +104,7 @@ function findGuardianExecutable(guardianRoot : StaticDirectory) : File { * d. guardian break: Look at processed data from previous step, return bad exit code if breaking results found and export results to file. */ @@public -export function runGuardian(args: GuardianArguments) : Transformer.ExecuteResult { +export function runGuardian(args: GuardianArguments, skipInstall?: boolean) : Transformer.ExecuteResult { validateArguments(args); let guardianResult = undefined; @@ -93,8 +115,13 @@ export function runGuardian(args: GuardianArguments) : Transformer.ExecuteResult else { const outputDirectory = Context.getNewOutputDirectory("guardianOut"); const guardianPaths = createGuardianPaths(outputDirectory, args.guardianPackageDirectory); - const guardianTool = getGuardianTool(args.guardianToolRootDirectory, guardianPaths); - let guardianDependencies : Transformer.InputArtifact[] = [args.guardianToolRootDirectory, f`${guardianPaths.globalGuardianRepo}`, args.guardianConfigFile]; + const guardianTool = getGuardianTool(args, guardianPaths); + let guardianDependencies : Transformer.InputArtifact[] = [ + args.guardianToolRootDirectory, + f`${guardianPaths.globalGuardianRepo}`, + args.guardianConfigFile, + ...addIfLazy(args.additionalDependencies !== undefined, () => args.additionalDependencies) + ]; // 0. Create a Guardian settings const genericSettingsFile = generateGenericGuardianSettingsFile(guardianPaths); @@ -106,7 +133,7 @@ export function runGuardian(args: GuardianArguments) : Transformer.ExecuteResult // 1. Initialize Guardian for this Guardian run // - Settings files from previous step not necessary here, can be run concurrently with the WriteFile operation. - const initializeResult = initializeGuardian(guardianTool, guardianPaths, guardianDependencies); + const initializeResult = initializeGuardian(args, guardianTool, guardianPaths, guardianDependencies); // Steps below this depend on the results of step 0 and step 1 guardianDependencies = guardianDependencies.concat([ @@ -115,12 +142,16 @@ export function runGuardian(args: GuardianArguments) : Transformer.ExecuteResult policyMicrosoft, policyNames ]); - - // 2. Run Guardian Install phase - const installResult = runGuardianInstall(args, guardianTool, installSettingsFile, guardianDependencies, guardianPaths); + + if (!skipInstall) + { + // 2. Run Guardian Install phase + const installResult = runGuardianInstall(args, guardianTool, installSettingsFile, guardianDependencies, guardianPaths); + guardianDependencies = guardianDependencies.push(installResult.getOutputFile(guardianPaths.installLog.path)); + } // 3. Guardian run to run static analysis tools specified in config file, break build if any breaking changes are found, and export results. - guardianDependencies = guardianDependencies.concat([installResult.getOutputFile(guardianPaths.installLog.path), genericSettingsFile]); + guardianDependencies = guardianDependencies.push(genericSettingsFile); guardianResult = runGuardianInternal(args, guardianTool, genericSettingsFile, guardianDependencies, guardianPaths); } @@ -191,7 +222,7 @@ function createGuardianPaths(outputDirectory : Directory, packageDirectory : Dir globalHistory: p`${Context.getMount("SourceRoot").path}/.gdn/internal.gdnhistory`, installLog: f`${outputDirectory}/install`, policyNamesPackageConfig: p`${outputDirectory}/buildxl_policy_names.gdnpackage`, - microsoftDefaultPolicyPackageConfig: p`${outputDirectory}/buildxl_policy_microsoft.gdnpackage` + microsoftDefaultPolicyPackageConfig: p`${outputDirectory}/buildxl_policy_microsoft.gdnpackage`, }; } @@ -262,17 +293,24 @@ function generateGuardianSettingsFile(outputPath : Path, settings : Object, desc /** * Initialize Guardian under a new output directory for this run. This will allow BuildXL to isolate each Guardian call into it's own repository. */ -function initializeGuardian(guardianTool : Transformer.ToolDefinition, guardianPaths : GuardianPaths, guardianDependencies : Transformer.InputArtifact[] ) : Transformer.ExecuteResult { +function initializeGuardian(args : GuardianArguments, guardianTool : Transformer.ToolDefinition, guardianPaths : GuardianPaths, guardianDependencies : Transformer.InputArtifact[]) : Transformer.ExecuteResult { + const arguments : Argument[] = [ + Cmd.argument("init"), + Cmd.argument("--force"), + Cmd.option("--logger-level ", args.logLevel !== undefined ? args.logLevel.toString() : undefined) + ]; + return Transformer.execute({ tool: guardianTool, tags: [ guardianTag ], - arguments: [Cmd.argument("init"), Cmd.argument("--force")], + arguments: arguments, workingDirectory: d`${guardianPaths.localGuardianRepo.parent}`, dependencies: guardianDependencies, outputs: [ d`${guardianPaths.localGuardianRepo}` ], successExitCodes: getSuccessExitCodes(), warningRegex: getWarningRegex(), - description: "Guardian Initialize" + description: "Guardian Initialize", + environmentVariables: getEnvironmentVariables(args) }); } @@ -287,6 +325,7 @@ function runGuardianInstall(args : GuardianArguments, guardianTool : Transformer Cmd.option("--settings-file ", settingsFile.path), Cmd.option("--config ", args.guardianConfigFile.path), Cmd.option("--logger-filepath ", guardianPaths.installLog.path), + Cmd.option("--logger-level ", args.logLevel !== undefined ? args.logLevel.toString() : undefined), /** TODO: Remove this option */ Cmd.option("--package-config ", Cmd.join(" ", [guardianPaths.policyNamesPackageConfig, guardianPaths.microsoftDefaultPolicyPackageConfig])) ]; @@ -303,7 +342,8 @@ function runGuardianInstall(args : GuardianArguments, guardianTool : Transformer acquireMutexes: [ guardianInstallMutex ], successExitCodes: getSuccessExitCodes(), warningRegex: getWarningRegex(), - description: "Guardian Install" + description: "Guardian Install", + environmentVariables: getEnvironmentVariables(args) }); } @@ -326,7 +366,9 @@ function runGuardianInternal(args : GuardianArguments, guardianTool : Transforme Cmd.option("--suppression-file ", Cmd.join(" ", args.suppressionFiles && args.suppressionFiles.map(e => e.path))), Cmd.options("--suppression-set ", args.suppressionSets), Cmd.flag("--no-policy", args.noPolicy), - Cmd.option("--policy ", args.policy) + Cmd.option("--policy ", args.policy), + Cmd.option("--logger-level ", args.logLevel !== undefined ? args.logLevel.toString() : undefined), + ...getBaselineOrSuppressOptions(args), ]; // Dependencies @@ -370,7 +412,8 @@ function runGuardianInternal(args : GuardianArguments, guardianTool : Transforme outputs: outputs, successExitCodes: getSuccessExitCodes(), warningRegex: getWarningRegex(), - description: "Guardian Run" + description: "Guardian Run", + environmentVariables: getEnvironmentVariables(args) }); return result; @@ -390,6 +433,53 @@ function getWarningRegex() : string { return Environment.getFlag(guardianWarnOnlyFlag) ? "\\[Error\\].*" : undefined; } +/** + * Merges cloudbuild and user specified environment variables. + */ +function getEnvironmentVariables(args : GuardianArguments) : Transformer.EnvironmentVariable[] { + const cbEnvVars = getCloudbuildEnvironmentVariables(); + + return args.environmentVariables === undefined + ? cbEnvVars + : args.environmentVariables.concat(cbEnvVars); // Cloudbuild variables should override user provided variables here +} + +/** + * If the TOOLPATH_GUARDIAN environment variable is set, then Guardian should be running on Cloudbuild. + * Add dotnet to the PATH variable for Guardian calls on Cloudbuild. + */ +function getCloudbuildEnvironmentVariables() : Transformer.EnvironmentVariable[] { + if (cloudbuildToolPath) { + return [ + { name: "PATH", separator: ";", value: [ d`${cloudBuildDotNetDirectory.path}` ] }, + { name: "DOTNET_ROOT", separator: ";", value: d`${cloudBuildDotNetDirectory.path}` }, + ]; + } + + return []; +} + +/** + * Special command line options for the guardian run command to automatically generate baselines or suppressions + * Note that this will disable breaking the build so that all baselines can be generated without the build stopping early due to a failure. + */ +function getBaselineOrSuppressOptions(args : GuardianArguments) : Argument[] { + if (Environment.getFlag(guardianGenerateBaselines)) { + return [ + Cmd.argument("--not-break-on-detections"), + Cmd.option("--output-baseline-file ", p`${args.autoGeneratedBaselineSuppressionLocation.path}/${args.baselineFileName}`), + ]; + } + else if (Environment.getFlag(guardianGenerateSuppressions)) { + return [ + Cmd.argument("--not-break-on-detections"), + Cmd.option("--output-suppression-file ", p`${args.autoGeneratedBaselineSuppressionLocation.path}/${args.suppressionFileName}`), + ]; + } + + return []; +} + /** * Set of guardian arguments. See notes on each argument for any special considerations that need to be * taken before using them. @@ -445,8 +535,31 @@ export interface GuardianArguments extends Transformer.RunnerArguments { severity?: string; /** Do not apply any policy */ noPolicy?: boolean; + /** Log level for Guardian to display to stdout */ + logLevel?: GuardianLogLevel; + /** Environment variables to be passed to Guardian */ + environmentVariables?: Transformer.EnvironmentVariable[]; + /** Location to place automatically generated baselines/suppressions */ + autoGeneratedBaselineSuppressionLocation?: Directory; + /** Name for automatically generated baseline file */ + baselineFileName?: PathAtom; + /** Name for automatically generated suppression file */ + suppressionFileName?: PathAtom; } +/** + * Guardian Log levels: + * + * None: (Silent) Do not output any messages, even if they are breaking. + * Standard: Standard messages intended for the user to see. + * Verbose: Messages for lower level actions a user may like to see, but are not necessary for them to understand the high level actions of the application. + * Warning: Non-breaking messages that are of greater importance for the user to see. Note: setting Warning also includes Error. + * Error: Breaking message that indicate something went wrong. + * Trace: Messages primarily intended for the application developer or a savy user to debug an application when it faults or gain insight during testing. Messages at this level should not be localized. + */ +@public +export type GuardianLogLevel = "None" | "Standard" | "Verbose" | "Warning" | "Error" | "Trace"; + /** * Collection of Paths that are produced or consumed by Guardian. */ diff --git a/Public/Sdk/Public/Tools/Guardian/deployment.dsc b/Public/Sdk/Public/Tools/Guardian/deployment.dsc new file mode 100644 index 000000000..c5d76999a --- /dev/null +++ b/Public/Sdk/Public/Tools/Guardian/deployment.dsc @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import * as Deployment from "Sdk.Deployment"; + +@@public +export const deployment : Deployment.Definition = { + contents: [ + f`Tool.Guardian.dsc`, + f`Tool.Guardian.CredScan.dsc`, + {file: f`LiteralFiles/module.config.dsc.literal`, targetFileName: a`module.config.dsc`}, + ] +}; diff --git a/Public/Sdk/UnitTests/Guardian/SdkTest.Guardian.dsc b/Public/Sdk/UnitTests/Guardian/SdkTest.Guardian.dsc index 000440d64..a9634eba2 100644 --- a/Public/Sdk/UnitTests/Guardian/SdkTest.Guardian.dsc +++ b/Public/Sdk/UnitTests/Guardian/SdkTest.Guardian.dsc @@ -8,7 +8,8 @@ namespace StandardSdk.Guardian { testFiles: globR(d`.`, "Test.*.dsc"), sdkFolders: [ d`${Context.getMount("SdkRoot").path}/Tools/Guardian`, - d`${Context.getMount("SdkRoot").path}/Json` + d`${Context.getMount("SdkRoot").path}/Json`, + d`${Context.getMount("SourceRoot").path}/Public/Sdk/Public/Deployment`, ], // autoFixLkgs: true // Uncomment this line to have all lkgs automatically updated. }); diff --git a/Public/Sdk/UnitTests/Guardian/Test.Guardian/runGuardian.lkg b/Public/Sdk/UnitTests/Guardian/Test.Guardian/runGuardian.lkg index 0aaf4d4fc..8c2d27c12 100644 --- a/Public/Sdk/UnitTests/Guardian/Test.Guardian/runGuardian.lkg +++ b/Public/Sdk/UnitTests/Guardian/Test.Guardian/runGuardian.lkg @@ -93,6 +93,8 @@ Transformer.execute({ p`./UserProfile/.nuget/plugins/netcore/CredentialProvider.Microsoft`, p`./UserProfile/.nuget/packages`, p`./path/to/guardian/packages`, + p`./path/to/guardian/temp`, + p`./path/to/guardian/tmp`, p`\${Context.getMount('Windows').path}`, p`\${Context.getMount('InternetCache').path}`, p`\${Context.getMount('InternetHistory').path}`, @@ -194,6 +196,8 @@ Transformer.execute({ p`./UserProfile/.nuget/plugins/netcore/CredentialProvider.Microsoft`, p`./UserProfile/.nuget/packages`, p`./path/to/guardian/packages`, + p`./path/to/guardian/temp`, + p`./path/to/guardian/tmp`, p`\${Context.getMount('Windows').path}`, p`\${Context.getMount('InternetCache').path}`, p`\${Context.getMount('InternetHistory').path}`, @@ -298,6 +302,8 @@ Transformer.execute({ p`./UserProfile/.nuget/plugins/netcore/CredentialProvider.Microsoft`, p`./UserProfile/.nuget/packages`, p`./path/to/guardian/packages`, + p`./path/to/guardian/temp`, + p`./path/to/guardian/tmp`, p`\${Context.getMount('Windows').path}`, p`\${Context.getMount('InternetCache').path}`, p`\${Context.getMount('InternetHistory').path}`, diff --git a/Public/Sdk/UnitTests/Json/SdkTest.Json.dsc b/Public/Sdk/UnitTests/Json/SdkTest.Json.dsc index a20497661..7147cc146 100644 --- a/Public/Sdk/UnitTests/Json/SdkTest.Json.dsc +++ b/Public/Sdk/UnitTests/Json/SdkTest.Json.dsc @@ -6,8 +6,9 @@ namespace StandardSdk.Json { export const hashingTest = BuildXLSdk.sdkTest({ testFiles: globR(d`.`, "Test.*.dsc"), sdkFolders: [ - d`${Context.getMount("SdkRoot").path}/Json` + d`${Context.getMount("SdkRoot").path}/Json`, + d`${Context.getMount("SourceRoot").path}/Public/Sdk/Public/Deployment`, ], - autoFixLkgs: true, // Uncomment this line to have all lkgs automatically updated. + // autoFixLkgs: true, // Uncomment this line to have all lkgs automatically updated. }); } diff --git a/Public/Src/App/Deployment.InBoxSdks.dsc b/Public/Src/App/Deployment.InBoxSdks.dsc index 5c1287b3a..b8bc50117 100644 --- a/Public/Src/App/Deployment.InBoxSdks.dsc +++ b/Public/Src/App/Deployment.InBoxSdks.dsc @@ -73,6 +73,18 @@ function createSdkDeploymentDefinition(serverDeployment: boolean, evaluationOnly importFrom("Sdk.JavaScript").deployment ] }, + { + subfolder: "Sdk.Json", + contents: [ + importFrom("Sdk.Json").deployment + ] + }, + { + subfolder: "Sdk.Guardian", + contents: [ + importFrom("BuildXL.Tools.Guardian").deployment + ] + }, ]) ] } diff --git a/Shared/Guardian/.gdn/README.md b/Shared/Guardian/.gdn/README.md new file mode 100644 index 000000000..6b6eb172c --- /dev/null +++ b/Shared/Guardian/.gdn/README.md @@ -0,0 +1 @@ +This empty directory is required for Guardian to run on a repo that does not have Guardian initialized. \ No newline at end of file diff --git a/Shared/Guardian/GuardianBuild.dsc b/Shared/Guardian/GuardianBuild.dsc new file mode 100644 index 000000000..2574dd7c9 --- /dev/null +++ b/Shared/Guardian/GuardianBuild.dsc @@ -0,0 +1,10 @@ +import * as Guardian from "Sdk.Guardian"; +import {Transformer} from "Sdk.Transformers"; + + +// Partially seal Guardian directory because Guardian may write temp files under the root directory +const guardianDirectory : Directory = d`${Environment.getPathValue("TOOLPATH_GUARDIAN")}/gdn`; +const guardianTool : StaticDirectory = Transformer.sealPartialDirectory(guardianDirectory, globR(guardianDirectory, "*")); + +// Guardian arguments will be automatically set by the SDK +export const guardianCredScanResult = Guardian.runCredScanOnEntireRepository(guardianTool, d`${Context.getMount("EnlistmentRoot").path}`); \ No newline at end of file diff --git a/Shared/Guardian/complianceConfig.dsc b/Shared/Guardian/complianceConfig.dsc new file mode 100644 index 000000000..79d7d1fea --- /dev/null +++ b/Shared/Guardian/complianceConfig.dsc @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +config({ + resolvers: [ + { + kind: "DScript", + modules: [ + { moduleName: "GuardianBuild", projects: [f`GuardianBuild.dsc`] } + ] + }, + ], + mounts: [ + { + // This config file may not be in the same directory as the enlistment root + name: a`EnlistmentRoot`, + isReadable: true, + isWritable: true, + isScrubbable: false, + path: Environment.getPathValue("BUILDXL_ENLISTMENT_ROOT"), + trackSourceFileChanges: true + }, + { + // The Guardian tool path on Cloudbuild contains the Guardian/GDNP packages, Guardian tool packages, and dotnet binaries + name: a`GuardianToolPath`, + isReadable: true, + isWritable: true, + isScrubbable: false, + path: Environment.getPathValue("TOOLPATH_GUARDIAN"), + trackSourceFileChanges: true + } + ] +}); \ No newline at end of file diff --git a/Shared/Guardian/guardianBuildConfig.gdnconfig b/Shared/Guardian/guardianBuildConfig.gdnconfig new file mode 100644 index 000000000..a3fc67375 --- /dev/null +++ b/Shared/Guardian/guardianBuildConfig.gdnconfig @@ -0,0 +1,35 @@ +{ + "fileVersion": "1.4", + "tools": [ + { + "fileVersion": "1.4", + "tool": { + "name": "CredScan", + "version": "2.1.17" + }, + "arguments": { + "TargetDirectory": "$(WorkingDirectory)/guardian.TSV", + "OutputType": "pre", + "SuppressAsError": true + }, + "outputExtension": "xml", + "successfulExitCodes": [ + 0, + 2, + 4, + 6 + ], + "errorExitCodes": { + "1": "Partial scan completed with warnings.", + "3": "Partial scan completed with credential matches and warnings.", + "5": "Partial scan completed with application warnings and credential matches", + "7": "Partial scan completed with application warnings, suppressed warnings, and credential matches", + "-1000": "Argument Exception.", + "-1100": "Invalid configuration.", + "-1500": "Configuration Exception.", + "-1600": "IO Exception.", + "-9000": "Unexpected Exception." + } + } + ] + } \ No newline at end of file