-
Notifications
You must be signed in to change notification settings - Fork 645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix issue 1043: Obfuscate AI data on request redirect #5236
Conversation
internal static bool NeedsObfuscation(string valueToBeVerified, out string obfuscatedTemplateKey) | ||
{ | ||
obfuscatedTemplateKey = string.Empty; | ||
var match = ObfuscatedTemplates.Where(template => Regex.IsMatch(valueToBeVerified.ToLower(), template.Key)).FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add an OrderBy
to the static so that order is defined. Dictionaries don't have a defined order AFAIK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use case insensitive regex instead of ToLower()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to use case insensitive.
Why to orderby? The return is only one element.
@@ -25,9 +51,55 @@ internal static class Obfuscator | |||
"Users/Profiles", | |||
"Users/ResetPassword"}; | |||
|
|||
internal static readonly Dictionary<string, ObfuscateMetadata> ObfuscatedTemplates = new Dictionary<string, ObfuscateMetadata> | |||
{ | |||
{@"/packages/(.+)/owners/(.+)/confirm/(.+)", new ObfuscateMetadata(@"/owners/(.+)/confirm", $"/owners/{DefaultTelemetryUserName}/confirm")}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably compile these regular expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we already use compiled regex expressions in the gallery? .NET only caches up to 15 compiled regex expressions by default (source here), and compiling a regex is rather expensive.
TLDR: Compiling regex may result in worse performance, we should do some due diligence before enabling it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice read, good point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did simple perf tests with: static regex and compiled instance regex . The results show similar perf characteristics.
For invocation of 1000 https://localhost/profiles/cristinamanu
Static - 525.6479932 seconds
Instance Compiled - 525.0719887 seconds
{@"/account/delete/(.+)", new ObfuscateMetadata(@"/account/delete/(.+)", $"/account/delete/{DefaultTelemetryUserName}") }, | ||
{@"/profiles/(.+)", new ObfuscateMetadata(@"/profiles/(.+)", $"/profiles/{DefaultTelemetryUserName}") }, | ||
{@"/account/setpassword/(.+)/(.+)", new ObfuscateMetadata(@"/setpassword/(.+)/", $"/setpassword/{DefaultTelemetryUserName}/") }, | ||
{@"/account/forgotpassword/(.+)/(.+)", new ObfuscateMetadata(@"/forgotpassword/(.+)/", $"/forgotpassword/{DefaultTelemetryUserName}/") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why @
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to use verbatim string when working with regex expressions. For these regex there is no difference.
{ | ||
return value; | ||
} | ||
string obfuscatedValue = Regex.Replace(value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this case sensitive? I think our URLs are:
https://www.nuget.org/PROFILES/jamesnk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added case insensitive option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⌚️
{ | ||
return url; | ||
} | ||
return new Uri($"{url.Scheme}://{url.Host}{Obfuscate(url.AbsolutePath)}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we lose port?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(e.g. if a customer is running gallery on a non-standard port)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the port.
@cristinamanum string regex for obfuscating urls seems fragile and error-prone. Would it be possible instead to just use the existing request's RouteData? Maybe we could even inject logic when mapping routes to specify what should be obfuscated? Very rough idea of what I'm thinking...
|
{ | ||
return value; | ||
} | ||
string obfuscatedValue = Regex.Replace(value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add timeouts to all regex matches: #4038
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the timeouts.
|
||
public static string ObfuscateUrlPath(this Route route, string urlPath) | ||
{ | ||
var path = route.Url; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra single whitespace? Or maybe it's the if
indentation below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
|
||
namespace NuGetGallery | ||
{ | ||
public static class RouteExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: class could be internal (tests already have internalsVisibleTo)
} | ||
} | ||
|
||
internal static Dictionary<string, ObfuscatedMetadata> ObfuscatedRouteMap = new Dictionary<string, ObfuscatedMetadata>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe name RoutePartsToObfuscateByUrl
is clearer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I like more the current name.
var metadata = ObfuscatedRouteMap[path]; | ||
string[] segments = urlPath.Split('/'); | ||
segments[metadata.ObfuscatedSegment] = metadata.ObfuscateValue; | ||
return segments.Aggregate((x, y) => { return x + "/" + y; }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this equivalent to String.Join('/', segments)
? Is Join simpler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep - join has better perf than aggregate - updated.
src/NuGetGallery/Helpers/RegexEx.cs
Outdated
@@ -8,7 +8,7 @@ namespace NuGetGallery.Helpers | |||
{ | |||
public static class RegexEx | |||
{ | |||
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); | |||
internal static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no - updated
{ | ||
// The new url form will be: https://nuget.org/ObfuscatedUserName | ||
return new Uri(Obfuscator.DefaultObfuscatedUrl(telemetryItem.Url)); | ||
return RouteTable.Routes.GetRouteData(new System.Web.HttpContextWrapper(HttpContext.Current)).Route as Route; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: namespace unnecessary since 'System.Web' is imported?
nit: maybe this is complex enough to be method like GetCurrentRouteData
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment 1 : it was not needed - updated
Comment 2 : I agree method is better suited - updated
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text.RegularExpressions; | ||
using NuGetGallery.Helpers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can revert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - updated
{ | ||
// The new url form will be: https://nuget.org/ObfuscatedUserName | ||
return new Uri(Obfuscator.DefaultObfuscatedUrl(telemetryItem.Url)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DefaultObfuscatedUrl was also called by QuietLog.GetObfuscatedServerVariables. Can this use the same technique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be updated if needed in a different change.
@cristinamanum Looks really nice! |
public static void MapRoute(this RouteCollection routes, string name, string url, object defaults, ObfuscatedMetadata obfuscationMetadata) | ||
{ | ||
routes.MapRoute(name, url, defaults); | ||
if (!ObfuscatedRouteMap.ContainsKey(url)) { ObfuscatedRouteMap.Add(url, obfuscationMetadata); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: { should be in a new line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return; | ||
} | ||
// Removes the first / | ||
var requestPath = requestTelemetryItem.Url.AbsolutePath.Remove(0,1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably a good idea to test if the first char is '/' before removal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use requestTelemetryItem.Url.AbsolutePath.TrimStart('/')
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested
updated with TrimStart
{ | ||
requestTelemetryItem.Url = new Uri(requestTelemetryItem.Url.ToString().Replace(requestPath, obfuscatedPath)); | ||
requestTelemetryItem.Name = requestTelemetryItem.Name.Replace(requestPath, obfuscatedPath); | ||
if(requestTelemetryItem.Context.Operation != null && requestTelemetryItem.Context.Operation.Name != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can simplify this: if (requestTelemetryItem.Context.Operation?.Name != null)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nicer - updated
The AI data can contain user names. The user names are in general on the request url field but in request redirect case it can be found on the name and operation_Name fields as well.
With this change:
A name like: GET /profiles/cristinamanu will be changed in GET /profiles/ObfuscatedUserName
An url like: http://localhost/profiles/cristinamanu will be changed in http://localhost/profiles/ObfuscatedUserName
A operation_Name like: GET /profiles/cristinamanu will be changed in GET /profiles/ObfuscatedUserName