-
Notifications
You must be signed in to change notification settings - Fork 867
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce enforcement mode for task restrictions (#3329)
Introduce enforcement mode for task restrictions Based on a feature variable, don't enforce restrictions from task.json, or just warn, or warn and enforce normally. Also support collecting telemetry on matching task restrictions, so we can see where they'd be applied before fully enabling them.
- Loading branch information
1 parent
c6c241a
commit 0cf2ef5
Showing
15 changed files
with
368 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
using Agent.Sdk.Knob; | ||
using Microsoft.TeamFoundation.Common; | ||
using Microsoft.TeamFoundation.DistributedTask.WebApi; | ||
using Microsoft.VisualStudio.Services.Agent.Util; | ||
using Microsoft.VisualStudio.Services.Agent.Worker.Telemetry; | ||
using Microsoft.VisualStudio.Services.WebPlatform; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Microsoft.VisualStudio.Services.Agent.Worker | ||
{ | ||
[ServiceLocator(Default = typeof(TaskRestrictionsChecker))] | ||
public interface ITaskRestrictionsChecker : IAgentService | ||
{ | ||
bool CheckCommand(IExecutionContext context, IWorkerCommand workerCommand, Command command); | ||
bool CheckSettableVariable(IExecutionContext context, string variable); | ||
} | ||
|
||
public sealed class TaskRestrictionsChecker : AgentService, ITaskRestrictionsChecker | ||
{ | ||
public bool CheckCommand(IExecutionContext context, IWorkerCommand workerCommand, Command command) | ||
{ | ||
ArgUtil.NotNull(context, nameof(context)); | ||
ArgUtil.NotNull(workerCommand, nameof(workerCommand)); | ||
ArgUtil.NotNull(command, nameof(command)); | ||
|
||
return Check( | ||
context, | ||
(TaskRestrictions restrictions) => restrictions.IsCommandAllowed(workerCommand), | ||
() => context.Warning(StringUtil.Loc("CommandNotAllowed", command.Area, command.Event)), | ||
() => context.Warning(StringUtil.Loc("CommandNotAllowedWarnOnly", command.Area, command.Event)), | ||
(TaskDefinitionRestrictions restrictions) => PublishCommandTelemetry(context, restrictions, command)); | ||
} | ||
|
||
public bool CheckSettableVariable(IExecutionContext context, string variable) | ||
{ | ||
ArgUtil.NotNull(context, nameof(context)); | ||
ArgUtil.NotNull(variable, nameof(variable)); | ||
|
||
return Check( | ||
context, | ||
(TaskRestrictions restrictions) => restrictions.IsSetVariableAllowed(variable), | ||
() => context.Warning(StringUtil.Loc("SetVariableNotAllowed", variable)), | ||
() => context.Warning(StringUtil.Loc("SetVariableNotAllowedWarnOnly", variable)), | ||
(TaskDefinitionRestrictions restrictions) => PublishVariableTelemetry(context, restrictions, variable)); | ||
} | ||
|
||
private bool Check( | ||
IExecutionContext context, | ||
Func<TaskRestrictions, bool> predicate, | ||
Action enforcedWarn, | ||
Action unenforcedWarn, | ||
Action<TaskDefinitionRestrictions> publishTelemetry) | ||
{ | ||
ArgUtil.NotNull(context, nameof(context)); | ||
ArgUtil.NotNull(predicate, nameof(predicate)); | ||
ArgUtil.NotNull(enforcedWarn, nameof(enforcedWarn)); | ||
ArgUtil.NotNull(unenforcedWarn, nameof(unenforcedWarn)); | ||
ArgUtil.NotNull(publishTelemetry, nameof(publishTelemetry)); | ||
|
||
var failedMatches = context.Restrictions?.Where(restrictions => !predicate(restrictions)); | ||
|
||
if (failedMatches.IsNullOrEmpty()) | ||
{ | ||
return true; | ||
} | ||
else | ||
{ | ||
var taskMatches = failedMatches.Where(restrictions => restrictions is TaskDefinitionRestrictions).Cast<TaskDefinitionRestrictions>(); | ||
|
||
if(AgentKnobs.EnableTaskRestrictionsTelemetry.GetValue(context).AsBoolean()) | ||
{ | ||
foreach(var match in taskMatches) | ||
{ | ||
publishTelemetry(match); | ||
} | ||
} | ||
|
||
string mode = AgentKnobs.TaskRestrictionsEnforcementMode.GetValue(context).AsString(); | ||
|
||
if (String.Equals(mode, "Enabled", StringComparison.OrdinalIgnoreCase) || taskMatches.Count() != failedMatches.Count()) | ||
{ | ||
// we are enforcing restrictions from tasks, or we matched restrictions from the pipeline, which we always enforce | ||
enforcedWarn(); | ||
return false; | ||
} | ||
else | ||
{ | ||
if (!String.Equals(mode, "Disabled", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
unenforcedWarn(); | ||
} | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
private void PublishCommandTelemetry(IExecutionContext context, TaskDefinitionRestrictions restrictions, Command command) | ||
{ | ||
ArgUtil.NotNull(context, nameof(context)); | ||
ArgUtil.NotNull(restrictions, nameof(restrictions)); | ||
ArgUtil.NotNull(command, nameof(command)); | ||
|
||
var data = new Dictionary<string, object>() | ||
{ | ||
{ "Command", $"{command.Area}.{command.Event}" } | ||
}; | ||
PublishTelemetry(context, restrictions, "TaskRestrictions_Command", data); | ||
} | ||
|
||
private void PublishVariableTelemetry(IExecutionContext context, TaskDefinitionRestrictions restrictions, string variable) | ||
{ | ||
ArgUtil.NotNull(context, nameof(context)); | ||
ArgUtil.NotNull(restrictions, nameof(restrictions)); | ||
ArgUtil.NotNull(variable, nameof(variable)); | ||
|
||
var data = new Dictionary<string, object>() | ||
{ | ||
{ "Variable", variable } | ||
}; | ||
PublishTelemetry(context, restrictions, "TaskRestrictions_SetVariable", data); | ||
} | ||
|
||
private void PublishTelemetry(IExecutionContext context, TaskDefinitionRestrictions restrictions, string feature, Dictionary<string, object> data) | ||
{ | ||
ArgUtil.NotNull(context, nameof(context)); | ||
ArgUtil.NotNull(restrictions, nameof(restrictions)); | ||
ArgUtil.NotNull(feature, nameof(feature)); | ||
ArgUtil.NotNull(data, nameof(data)); | ||
|
||
data.Add("TaskName", restrictions.Definition.Name); | ||
data.Add("TaskVersion", restrictions.Definition.Version); | ||
|
||
CustomerIntelligenceEvent ciEvent = new CustomerIntelligenceEvent() | ||
{ | ||
Area = "AzurePipelinesAgent", | ||
Feature = feature, | ||
Properties = data | ||
}; | ||
|
||
var publishCommand = new PublishTelemetryCommand(); | ||
publishCommand.PublishEvent(context, ciEvent); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using Microsoft.TeamFoundation.DistributedTask.WebApi; | ||
using Microsoft.VisualStudio.Services.Agent.Util; | ||
using Minimatch; | ||
using System; | ||
|
||
namespace Microsoft.VisualStudio.Services.Agent.Worker | ||
{ | ||
[ AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] | ||
public sealed class CommandRestrictionAttribute : Attribute | ||
{ | ||
public bool AllowedInRestrictedMode { get; set; } | ||
} | ||
|
||
public static class TaskRestrictionExtension | ||
{ | ||
public static Boolean IsCommandAllowed(this TaskRestrictions restrictions, IWorkerCommand command) | ||
{ | ||
ArgUtil.NotNull(command, nameof(command)); | ||
|
||
if (restrictions.Commands?.Mode == TaskCommandMode.Restricted) | ||
{ | ||
foreach (var attr in command.GetType().GetCustomAttributes(typeof(CommandRestrictionAttribute), false)) | ||
{ | ||
var cra = attr as CommandRestrictionAttribute; | ||
if (cra.AllowedInRestrictedMode) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
else | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
public static Boolean IsSetVariableAllowed(this TaskRestrictions restrictions, String variable) | ||
{ | ||
ArgUtil.NotNull(variable, nameof(variable)); | ||
|
||
var allowedList = restrictions.SettableVariables?.Allowed; | ||
if (allowedList == null) | ||
{ | ||
return true; | ||
} | ||
|
||
var opts = new Options() { IgnoreCase = true }; | ||
foreach (String pattern in allowedList) | ||
{ | ||
if (Minimatcher.Check(variable, pattern, opts)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
|
||
public sealed class TaskDefinitionRestrictions : TaskRestrictions | ||
{ | ||
public TaskDefinitionRestrictions(DefinitionData definition) : base() | ||
{ | ||
Definition = definition; | ||
Commands = definition.Restrictions?.Commands; | ||
SettableVariables = definition.Restrictions?.SettableVariables; | ||
} | ||
|
||
public DefinitionData Definition { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.