Skip to content

Commit 9667e03

Browse files
Users/v kivlev/backport redaction to 3.227.2 (#4467)
* Restore expanded redaction as a default feature. Add a set of new, highly accurate detections. * Simplify provider name. * Add feature flag support * Cleanup * Set off by default / remove knob, add tracing * added extension / fixed tests * remove duplicate masker, changes related to tests * Update tests, remove extra blank lines * update agent version --------- Co-authored-by: Michael C. Fanning <[email protected]> Co-authored-by: v-kivlev <undefined>
1 parent ba93fcd commit 9667e03

File tree

10 files changed

+110
-188
lines changed

10 files changed

+110
-188
lines changed

src/Agent.Listener/Configuration/FeatureFlagProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public async Task<FeatureFlag> GetFeatureFlagAsync(IHostContext context, string
4949
var client = vssConnection.GetClient<FeatureAvailabilityHttpClient>();
5050
try
5151
{
52-
return await client.GetFeatureFlagByNameAsync(featureFlagName);
52+
return await client.GetFeatureFlagByNameAsync(featureFlagName, checkFeatureExists: false);
5353
} catch(VssServiceException e)
5454
{
5555
Trace.Warning("Unable to retrive feature flag status: " + e.ToString());

src/Agent.Listener/JobDispatcher.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
using System.Linq;
1818
using Microsoft.VisualStudio.Services.Common;
1919
using System.Diagnostics;
20-
20+
using Agent.Listener.Configuration;
2121

2222
namespace Microsoft.VisualStudio.Services.Agent.Listener
2323
{
@@ -88,6 +88,18 @@ public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce
8888
}
8989
}
9090

91+
var service = HostContext.GetService<IFeatureFlagProvider>();
92+
string ffState;
93+
try
94+
{
95+
ffState = service.GetFeatureFlagAsync(HostContext, "DistributedTask.Agent.EnableAdditionalMaskingRegexes", Trace)?.Result?.EffectiveState;
96+
}
97+
catch (Exception)
98+
{
99+
ffState = "Off";
100+
}
101+
jobRequestMessage.Variables[Constants.Variables.Features.EnableAdditionalMaskingRegexes] = ffState;
102+
91103
WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId);
92104
if (runOnce)
93105
{

src/Agent.Sdk/Knob/AgentKnobs.cs

-6
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,6 @@ public class AgentKnobs
284284
new EnvironmentKnobSource("SYSTEM_UNSAFEALLOWMULTILINESECRET"),
285285
new BuiltInDefaultKnobSource("false"));
286286

287-
public static readonly Knob MaskUsingCredScanRegexes = new Knob(
288-
nameof(MaskUsingCredScanRegexes),
289-
"Use the CredScan regexes for masking secrets. CredScan is an internal tool developed at Microsoft to keep passwords and authentication keys from being checked in. This defaults to disabled, as there are performance problems with some task outputs.",
290-
new EnvironmentKnobSource("AZP_USE_CREDSCAN_REGEXES"),
291-
new BuiltInDefaultKnobSource("false"));
292-
293287
public static readonly Knob MaskedSecretMinLength = new Knob(
294288
nameof(MaskedSecretMinLength),
295289
"Specify the length of the secrets, which, if shorter, will be ignored in the logs.",

src/Agent.Worker/Worker.cs

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using Microsoft.VisualStudio.Services.WebApi;
1212
using Agent.Sdk.Util;
13+
using Agent.Sdk.Knob;
1314

1415
namespace Microsoft.VisualStudio.Services.Agent.Worker
1516
{
@@ -67,6 +68,19 @@ public async Task<int> RunAsync(string pipeIn, string pipeOut)
6768
InitializeSecretMasker(jobMessage);
6869
SetCulture(jobMessage);
6970

71+
var maskUsingCredScanRegexesState = "Off";
72+
73+
if(jobMessage.Variables.TryGetValue(Constants.Variables.Agent.EnableAdditionalMaskingRegexes, out var enableAdditionalMaskingRegexes))
74+
{
75+
maskUsingCredScanRegexesState = enableAdditionalMaskingRegexes.Value;
76+
}
77+
78+
if(maskUsingCredScanRegexesState == "On")
79+
{
80+
Trace.Verbose($"{Constants.Variables.Agent.EnableAdditionalMaskingRegexes} is On, adding additional masking regexes");
81+
HostContext.AddAdditionalMaskingRegexes();
82+
}
83+
7084
// Start the job.
7185
Trace.Info($"Job message:{Environment.NewLine} {StringUtil.ConvertToJson(WorkerUtilities.ScrubPiiData(jobMessage))}");
7286
Task<TaskResult> jobRunnerTask = jobRunner.RunAsync(jobMessage, jobRequestCancellationToken.Token);

src/Microsoft.VisualStudio.Services.Agent/AdditionalMaskingRegexes.CredScan.cs

+39-165
Original file line numberDiff line numberDiff line change
@@ -30,176 +30,50 @@ public static partial class AdditionalMaskingRegexes
3030
private static IEnumerable<string> credScanPatterns =
3131
new List<string>()
3232
{
33-
// AnsibleVaultData
34-
@"" // pre-match
35-
+ @"\$ANSIBLE_VAULT;\d+\.\d+;AES256\s+\d+" // match
33+
// AAD client app, most recent two versions.
34+
@"\b" // pre-match
35+
+ @"[0-9A-Za-z-_~.]{3}7Q~[0-9A-Za-z-_~.]{31}\b|\b[0-9A-Za-z-_~.]{3}8Q~[0-9A-Za-z-_~.]{34}" // match
36+
+ @"\b", // post-match
37+
38+
// Prominent Azure provider 512-bit symmetric keys.
39+
@"\b" // pre-match
40+
+ @"[0-9A-Za-z+/]{76}(APIM|ACDb|\+(ABa|AMC|ASt))[0-9A-Za-z+/]{5}[AQgw]==" // match
3641
+ @"", // post-match
37-
38-
// AzurePowerShellTokenCache
39-
@"" // pre-match
40-
+ @"[""']TokenCache[""']\s*:\s*\{\s*[""']CacheData[""']\s*:\s*[""'][a-z0-9/\+]{86}" // match
41-
+ @"", // post-match
42-
43-
// Base64EncodedStringLiteral
44-
@"(?<=(^|[""'>;=\s#]))" // pre-match
45-
+ @"(?<DataBlock>(?-i)MI(?i)[a-z0-9/+\s\u0085\u200b""',\\]{200,20000}[a-z0-9/+]={0,2})" // match
42+
//
43+
// Prominent Azure provider 256-bit symmetric keys.
44+
@"\b" // pre-match
45+
+ @"[0-9A-Za-z+/]{33}(AIoT|\+(ASb|AEh|ARm))[A-P][0-9A-Za-z+/]{5}=" // match
4646
+ @"", // post-match
47-
48-
// JsonWebToken
49-
@"" // pre-match
50-
+ @"(?-i)(?<JwtToken>eyJ(?i)[a-z0-9\-_%]+\.(?-i)eyJ(?i)[a-z0-9\-_%]+\.[a-z0-9\-_%]+)|([rR]efresh_?[tT]oken|REFRESH_?TOKEN)[""']?\s*[:=]{1,2}\s*[""']?(?<JwtToken>(\w+-)+\w+)[""']?" // match
51-
+ @"", // post-match
52-
53-
// SlackTokens
54-
@"" // pre-match
55-
+ @"xox[pbar]\-[a-z0-9\-]+" // match
56-
+ @"", // post-match
57-
58-
// SymmetricKey128
59-
@"(?<=[^\w/\+\._\$,\\])" // pre-match
60-
+ @"(?<SymmetricKey>[a-z0-9/\+]{22}==)" // match
61-
+ @"(?=([^\w/\+\.\$]|$))", // post-match
62-
63-
// SymmetricKey128Hex
64-
@"(?<=[^\w/\+\._\$,\\][dapi]+)" // pre-match
65-
+ @"(?<SymmetricKey>[a-f0-9]{32})" // match
66-
+ @"(?=([^\w/\+\.\$]|$))", // post-match
67-
68-
// SymmetricKey160Hex
69-
@"(?<=[^\w/\+\._\$,\\])" // pre-match
70-
+ @"(?<Hex160>[a-f0-9/\+]{40})" // match
71-
+ @"(?=([^\w/\+\.\$]|$))", // post-match
72-
73-
// SymmetricKey232
74-
@"(?<=[^\w/\+\._\$,\\])" // pre-match
75-
+ @"(?<SymmetricKey>(?-i)AIza(?i)[a-z0-9_\\\-]{35})" // match
76-
+ @"(?=([^\w/\+\.\$]|$))", // post-match
77-
78-
// SymmetricKey240
79-
@"(?<=[^\w/\+\.\-\$,\\])" // pre-match
80-
+ @"(?<SymmetricKey>[a-z0-9/\+]{40})" // match
81-
+ @"(?=([^\w/\+\.\-\$,\\]|$))", // post-match
82-
83-
// SymmetricKey256
84-
@"(?<=[^\w/\+\.\$,\\])" // pre-match
85-
+ @"(?<SymmetricKey>[a-z0-9/\+]{43}=)" // match
86-
+ @"(?=([^\w/\+\.\$]|$))", // post-match
87-
88-
// SymmetricKey256B32
89-
@"(?<=[^\w/\+\._\-\$,\\])" // pre-match
90-
+ @"(?<SymmetricKey>(?-i)[a-z2-7]{52}(?i))" // match
91-
+ @"(?=(?<=[0-9]+[a-z]+[0-9]+.{0,49})([^\w/\+\.\-\$,]|$))", // post-match
92-
93-
// SymmetricKey256UrlEncoded
94-
@"(?<=[^\w/\+\._\-\$,\\%])" // pre-match
95-
+ @"(?<SymmetricKey>[a-z0-9%]{43,63}%3d)" // match
47+
48+
// Azure Function key.
49+
@"\b" // pre-match
50+
+ @"[0-9A-Za-z_\-]{44}AzFu[0-9A-Za-z\-_]{5}[AQgw]==" // match
9651
+ @"", // post-match
9752

98-
// SymmetricKey320
99-
@"(?<=[^\w/\+\.\-\$,\\])" // pre-match
100-
+ @"(?<SymmetricKey>[a-z0-9/\+]{54}={2})" // match
101-
+ @"(?=([^\w/\+\.\-\$,\\]|$))", // post-match
102-
103-
// SymmetricKey320UrlEncoded
104-
@"(?<=[^\w/\+\.\-\$,\\%])" // pre-match
105-
+ @"(?<SymmetricKey>[a-z0-9%]{54,74}(%3d){2})" // match
53+
// Azure Search keys.
54+
@"\b" // pre-match
55+
+ @"[0-9A-Za-z]{42}AzSe[A-D][0-9A-Za-z]{5}" // match
56+
+ @"\b", // post-match
57+
58+
// Azure Container Registry keys.
59+
@"\b" // pre-match
60+
+ @"[0-9A-Za-z+/]{42}\+ACR[A-D][0-9A-Za-z+/]{5}" // match
61+
+ @"\b", // post-match
62+
63+
// Azure Cache for Redis keys.
64+
@"\b" // pre-match
65+
+ @"[0-9A-Za-z]{33}AzCa[A-P][0-9A-Za-z]{5}=" // match
10666
+ @"", // post-match
107-
108-
// SymmetricKey360
109-
@"(?<=[^\w/\+\.\-\$,\\])" // pre-match
110-
+ @"(?<SymmetricKey>[a-z0-9/\+]{60})" // match
111-
+ @"(?=[^\w/\+\.\-\$,\\])", // post-match
112-
113-
// SymmetricKey512
114-
@"(?<=[^\r\n\t\w/\+\.\-\$,\\])" // pre-match
115-
+ @"(?<SymmetricKey>[a-z0-9/\+]{86}==)" // match
116-
+ @"(?=([^\w/\+\.\-\$]|$))", // post-match
117-
118-
// AzureActiveDirectoryLoginCredentials
119-
@"(?<=@([a-z0-9.]+\.(on)?)?microsoft\.com[ -~\s\u200b]{1,80}?(userpass|password|pwd|pw|\wpass[ =:>]+|(get|make)securestring)\W)" // pre-match
120-
+ @"(?<Password>[^\s;`,""'\(\)]{10,80})" // match
121-
+ @"(?=[\s;`,""'\(\)])", // post-match
122-
123-
// AzureActiveDirectoryLoginCredentials
124-
@"" // pre-match
125-
+ @"(?<MigrationPassword>(\-destinationPasswordPlain ""[^""]+?""))" // match
126-
+ @"(?=[ -~\s\u200b]{1,150}?@([a-z0-9.]+\.(on)?)?microsoft\.com)", // post-match
127-
128-
// AzureActiveDirectoryLoginCredentials
129-
@"(?<=(sign_in|SharePointOnlineAuthenticatedContext|(User|Exchange)Credentials?|password)[ -~\s\u200b]{1,100}?@([a-z0-9.]+\.(on)?)?microsoft\.com['""]?( \|\| \w+)?\s*,[\s\u200b]['""]?)" // pre-match
130-
+ @"(?<ArgumentPassword>[^`'""\s,;\(\)]+?)" // match
131-
+ @"(?=[`'""\s,;\(\)])", // post-match
132-
133-
// AzureActiveDirectoryLoginCredentials
134-
@"" // pre-match
135-
+ @"(?<PrevPassword>password[\W_][ -~\s\u200b]{40,100}?)" // match
136-
+ @"(?=@([a-z0-9\.]+\.(on)?)?microsoft\.com)", // post-match
137-
138-
// LoginCredentials
139-
@"" // pre-match
140-
+ @"[^a-z\$](DB_USER|user id|uid|(sql)?user(name)?|service\s?account)\s*[^\w\s,]([ -~\s\u200b]{2,120}?|[ -~]{2,30}?)([^a-z\s\$]|\s)\s*(DB_PASS|(sql|service)?password|pwd)\s*[^a-z,\+&\)\]\}\[\{_][ -~\s\u200b]{2,700}?([;|<,})]|$)|[^a-z\s\$]\s*(DB_PASS|password|pwd)\s*[^a-z,\+&\)\]\}\[\{_][ -~\s\u200b]{2,60}?[^a-z\$](DB_USER|user id|uid|user(name)?)\s*[^\w\s,]([ -~\s\u200b]{2,60}?|[ -~]{2,30}?)([;|<,})]|$)" // match
141-
+ @"", // post-match
142-
143-
// LoginCredentialsInUrl
144-
@"(?<=(amqp|ssh|(ht|f)tps?)://[^%:\s""'/][^:\s""'/\$]+[^:\s""'/\$%]:)" // pre-match
145-
+ @"(?<Password>[^%\s""'/][^@\s""'/]{0,100}[^%\s""'/])" // match
146-
+ @"(?=@[\$a-z0-9:\.\-_%\?=/]+)", // post-match
147-
148-
// CertificatePrivateKeyHeader
149-
@"" // pre-match
150-
+ @"(?-i)\-{5}BEGIN( ([DR]SA|EC|OPENSSH|PGP))? PRIVATE KEY( BLOCK)?\-{5}" // match
151-
+ @"", // post-match
152-
153-
// HttpAuthorizationHeader
154-
@"(?<=authorization[,\[:= ""']+(basic|digest|hoba|mutual|negotiate|oauth( oauth_token=)?|bearer [^e""'&]|scram\-sha\-1|scram\-sha\-256|vapid|aws4\-hmac\-sha256)[\s\r\n]{0,10})" // pre-match
155-
+ @"(?<Token>[a-z0-9/+_.=]{10,})" // match
156-
+ @"", // post-match
157-
158-
// ClientSecretContext
159-
@"(?<=(^|[a-z\s""'_])((app(lication)?|client)[_\- ]?(key(url)?|secret)|refresh[_\-]?token|[^t]AccessToken(Secret)?|(Consumer|api)[_\- ]?(Secret|Key)|(Twilio(Account|Auth)[_\- ]?(Sid|Token)))([\s=:>]{1,10}|[\s""':=|>,]{3,15}|[""'=:\(]{2}))" // pre-match
160-
+ @"(?<ClientSecret>(""data:text/plain,.+""|[a-z0-9/+=_.-]{10,200}[^\(\[\{;,\r\n]|[^\s""';<,\)]{5,200}))" // match
161-
+ @"", // post-match
162-
163-
// CommunityStringContext
164-
@"(?<=(^|\W{2}|set )snmp(\-server)?( | [ -~]+? )(community|priv)\s[""']?)" // pre-match
165-
+ @"(?<CommunityString>[^\s]+)" // match
166-
+ @"(?=[""']?(\s|$))", // post-match
167-
168-
// PasswordContextInScript
169-
@"(?<=\s-(admin|user|vm)?password\s+[""']?)" // pre-match
170-
+ @"(?<ScriptArgumentPassword>[^$\(\[<\{\-\s,""']+)[""']?(\s|$)" // match
171-
+ @"", // post-match
172-
173-
// PasswordContextInScript
174-
@"(?<=certutil(\.exe)?.{1,10}\-p\s+[""']?)" // pre-match
175-
+ @"(?<CertUtilPassword>[^\s,]{2,50})" // match
176-
+ @"(?=[""']?)", // post-match
177-
178-
// PasswordContextInScript
179-
@"(?<=(^|[_\s\$])[a-z]*(password|secret(key)?)[ \t]*[=:]+[ \t]*)" // pre-match
180-
+ @"(?<ScriptAssignmentPassword>[^:\s""';,<]{2,200})" // match
181-
+ @"", // post-match
182-
183-
// PasswordContextInScript
184-
@"(?<=\s-Name\s+[""']\w+Password[""']\s+-Value\s+[""']?)" // pre-match
185-
+ @"(?<RegistryPassword>[^\s""']{2,1100})" // match
186-
+ @"(?=[""']?)", // post-match
187-
188-
// PasswordContextInScript
189-
@"(?<=(^|[\s\r\n\\])net(\.exe)?[""'\s\\]{1,5}(user\s+|share\s+/user:)[^\s,/]+[ \t]+[""']?)" // pre-match
190-
+ @"(?<NetUsePassword>[^\s,""'>/]{2,50})" // match
191-
+ @"(?=[""']?)", // post-match
192-
193-
// PasswordContextInScript
194-
@"(?<=psexec(\.exe)?.{1,50}-u.{1,50}-p\s+[""']?)" // pre-match
195-
+ @"(?<PsExecPassword>[^\s,]{2,50})" // match
196-
+ @"(?=[""']?)", // post-match
197-
198-
// SymmetricKeyContextInXml
199-
@"" // pre-match
200-
+ @"<(machineKey|parameter name=""|[a-z]+AccountInfo[^a-z])" // match
201-
+ @"", // post-match
202-
67+
68+
// NuGet API keys.
69+
@"\b" // pre-match
70+
+ @"oy2[a-p][0-9a-z]{15}[aq][0-9a-z]{11}[eu][bdfhjlnprtvxz357][a-p][0-9a-z]{11}[aeimquy4]" // match
71+
+ @"\b", // post-match
72+
73+
// NPM author keys.
74+
@"\b" // pre-match
75+
+ @"npm_[0-9A-Za-z]{36}" // match
76+
+ @"\b", // post-match
20377
};
20478
}
20579
}

src/Microsoft.VisualStudio.Services.Agent/Constants.cs

+3
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ public static class Agent
322322
public static readonly string Version = "agent.version";
323323
public static readonly string WorkFolder = "agent.workfolder";
324324
public static readonly string WorkingDirectory = "agent.WorkingDirectory";
325+
public static readonly string EnableAdditionalMaskingRegexes = "agent.enableadditionalmaskingregexes";
325326
}
326327

327328
public static class Build
@@ -371,6 +372,7 @@ public static class Features
371372
public static readonly string GitLfsSupport = "agent.source.git.lfs";
372373
public static readonly string GitShallowDepth = "agent.source.git.shallowFetchDepth";
373374
public static readonly string SkipSyncSource = "agent.source.skip";
375+
public static readonly string EnableAdditionalMaskingRegexes = "agent.enableadditionalmaskingregexes";
374376
}
375377

376378
public static class Maintenance
@@ -511,6 +513,7 @@ public static class Task
511513
Agent.Version,
512514
Agent.WorkFolder,
513515
Agent.WorkingDirectory,
516+
Agent.EnableAdditionalMaskingRegexes,
514517
// Build variables
515518
Build.ArtifactStagingDirectory,
516519
Build.BinariesDirectory,

src/Microsoft.VisualStudio.Services.Agent/HostContext.cs

+11-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Net.Http.Headers;
2222
using Pipelines = Microsoft.TeamFoundation.DistributedTask.Pipelines;
2323
using Agent.Sdk.Util;
24+
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
2425

2526
namespace Microsoft.VisualStudio.Services.Agent
2627
{
@@ -88,6 +89,7 @@ public class HostContext : EventListener, IObserver<DiagnosticListener>, IObserv
8889
public ShutdownReason AgentShutdownReason { get; private set; }
8990
public ILoggedSecretMasker SecretMasker => _secretMasker;
9091
public ProductInfoHeaderValue UserAgent => _userAgent;
92+
9193
public HostContext(HostType hostType, string logFile = null)
9294
{
9395
_secretMasker = new LoggedSecretMasker(_basicSecretMasker);
@@ -106,13 +108,6 @@ public HostContext(HostType hostType, string logFile = null)
106108
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape, $"HostContext_{WellKnownSecretAliases.UriDataEscape}");
107109
this.SecretMasker.AddValueEncoder(ValueEncoders.BackslashEscape, $"HostContext_{WellKnownSecretAliases.UriDataEscape}");
108110
this.SecretMasker.AddRegex(AdditionalMaskingRegexes.UrlSecretPattern, $"HostContext_{WellKnownSecretAliases.UrlSecretPattern}");
109-
if (AgentKnobs.MaskUsingCredScanRegexes.GetValue(this).AsBoolean())
110-
{
111-
foreach (var pattern in AdditionalMaskingRegexes.CredScanPatterns)
112-
{
113-
this.SecretMasker.AddRegex(pattern, $"HostContext_{WellKnownSecretAliases.CredScanPatterns}");
114-
}
115-
}
116111

117112
// Create the trace manager.
118113
if (string.IsNullOrEmpty(logFile))
@@ -741,6 +736,15 @@ public static HttpClientHandler CreateHttpClientHandler(this IHostContext contex
741736

742737
return clientHandler;
743738
}
739+
740+
public static void AddAdditionalMaskingRegexes(this IHostContext context)
741+
{
742+
ArgUtil.NotNull(context, nameof(context));
743+
foreach (var pattern in AdditionalMaskingRegexes.CredScanPatterns)
744+
{
745+
context.SecretMasker.AddRegex(pattern, $"HostContext_{WellKnownSecretAliases.CredScanPatterns}");
746+
}
747+
}
744748
}
745749

746750
public enum ShutdownReason

0 commit comments

Comments
 (0)