diff --git a/src/Agent.Worker/TaskDecoratorManager.cs b/src/Agent.Worker/TaskDecoratorManager.cs new file mode 100644 index 0000000000..7d3e42bbc5 --- /dev/null +++ b/src/Agent.Worker/TaskDecoratorManager.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace Microsoft.VisualStudio.Services.Agent.Worker +{ + [ServiceLocator(Default = typeof(TaskDecoratorManager))] + public interface ITaskDecoratorManager : IAgentService + { + bool IsInjectedTaskForTarget(string taskName); + bool IsInjectedInputsContainsSecrets(Dictionary inputs, out List inputsWithSecrets); + string GenerateTaskResultMessage(List inputsWithSecrets); + } + + public sealed class TaskDecoratorManager : AgentService, ITaskDecoratorManager + { + /// + /// Checks if current task is injected by decorator with posttargettask or pretargettask target + /// + /// Name of the task to check + /// Returns `true` if task is injected by decorator for target task, otherwise `false` + public bool IsInjectedTaskForTarget(string taskName) + { + return taskName.StartsWith(InjectedTasksNamesPrefixes.PostTargetTask) + || taskName.StartsWith(InjectedTasksNamesPrefixes.PreTargetTask); + } + + /// + /// Verifies that there are inputs with secrets, if secrets were found task will be marked as skipped and won't be executed + /// + /// Inputs presented as a dictionary with input name as key and input's value as the value of the corresponding key + /// Out value that will contain the list of task inputs with secrets + /// Return `true` if task contains injected inputs with secrets, otherwise `false` + public bool IsInjectedInputsContainsSecrets(Dictionary inputs, out List inputsWithSecrets) + { + inputsWithSecrets = this.GetInputsWithSecrets(inputs); + + return inputsWithSecrets.Count > 0; + } + + /// + /// Generates list of inputs that should be included into task result message + /// + /// List of inputs with secrets, that should be included in message + public string GenerateTaskResultMessage(List inputsWithSecrets) + { + string inputsForReport = string.Join(Environment.NewLine, + inputsWithSecrets.Select(input => string.Join("\n", input))); + + return inputsForReport; + } + + /// + /// Used to check if provided input value contain any secret + /// + /// Value of input to check + /// Returns `true` if provided string contain secret, otherwise `false` + private bool ContainsSecret(string inputValue) + { + string maskedString = HostContext.SecretMasker.MaskSecrets(inputValue); + return maskedString != inputValue; + } + + /// + /// Used to get list of inputs in injected task that started with target_ prefix and contain secrets, + /// such inputs are autoinjected from target tasks + /// + /// Inputs presented as a dictionary with input name as key and input's value as the value of the corresponding key + /// Returns list of inputs' names that contain secret values + private List GetInputsWithSecrets(Dictionary inputs) + { + var inputsWithSecrets = new List(); + foreach (var input in inputs) + { + if (input.Key.StartsWith("target_") && this.ContainsSecret(input.Value)) + { + inputsWithSecrets.Add(input.Key); + } + } + + return inputsWithSecrets; + } + } + + internal static class InjectedTasksNamesPrefixes + { + public static readonly String PostTargetTask = "__system_posttargettask_"; + public static readonly String PreTargetTask = "__system_pretargettask_"; + } +} diff --git a/src/Agent.Worker/TaskRunner.cs b/src/Agent.Worker/TaskRunner.cs index 56917596be..34ceaa67c2 100644 --- a/src/Agent.Worker/TaskRunner.cs +++ b/src/Agent.Worker/TaskRunner.cs @@ -211,6 +211,22 @@ public async Task RunAsync() // Expand the inputs. Trace.Verbose("Expanding inputs."); runtimeVariables.ExpandValues(target: inputs); + + // We need to verify inputs of the tasks that were injected by decorators, to check if they contain secrets, + // for security reasons execution of tasks in this case should be skipped. + // Target task inputs could be injected into the decorator's tasks if the decorator has post-task-tasks or pre-task-tasks targets, + // such tasks will have names that start with __system_pretargettask_ or __system_posttargettask_. + var taskDecoratorManager = HostContext.GetService(); + if (taskDecoratorManager.IsInjectedTaskForTarget(Task.Name) && + taskDecoratorManager.IsInjectedInputsContainsSecrets(inputs, out var inputsWithSecrets)) + { + var inputsForReport = taskDecoratorManager.GenerateTaskResultMessage(inputsWithSecrets); + + ExecutionContext.Result = TaskResult.Skipped; + ExecutionContext.ResultCode = StringUtil.Loc("SecretsAreNotAllowedInInjectedTaskInputs", inputsForReport); + return; + } + VarUtil.ExpandEnvironmentVariables(HostContext, target: inputs); // Translate the server file path inputs to local paths. diff --git a/src/Misc/layoutbin/en-US/strings.json b/src/Misc/layoutbin/en-US/strings.json index 6c6862641a..af39211c0f 100644 --- a/src/Misc/layoutbin/en-US/strings.json +++ b/src/Misc/layoutbin/en-US/strings.json @@ -561,6 +561,7 @@ "ScanToolCapabilities": "Scanning for tool capabilities.", "ScreenSaverPoliciesInspection": "Checking for policies that may prevent screensaver from being disabled.", "ScreenSaverPolicyWarning": "Screensaver policy is defined on the machine. This may lead to screensaver being enabled again. Active screensaver may impact UI operations, for e.g., automated UI tests may fail.", + "SecretsAreNotAllowedInInjectedTaskInputs": "Task is trying to access the following inputs of a target task which contain secrets:\n{0}\nIt is not allowed to pass inputs that contain secrets to the tasks injected by decorators.", "SelfManageGitCreds": "You are in self manage git creds mode. Make sure your agent host machine can bypass any git authentication challenge.", "ServerTarpit": "The job is currently being throttled by the server. You may experience delays in console line output, job status reporting, and task log uploads.", "ServerTarpitUrl": "Link to resource utilization page (global 1-hour view): {0}.",