Skip to content
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

Merged
merged 7 commits into from
Jan 10, 2018
Merged

Conversation

cristinamanum
Copy link
Contributor

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:

  1. The redirect requests data will be obfuscated
    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
  2. The not redirected requests will be obfuscated and kept in sync with the above structure. A snapshot of the obfuscated values:
    image

internal static bool NeedsObfuscation(string valueToBeVerified, out string obfuscatedTemplateKey)
{
obfuscatedTemplateKey = string.Empty;
var match = ObfuscatedTemplates.Where(template => Regex.IsMatch(valueToBeVerified.ToLower(), template.Key)).FirstOrDefault();
Copy link
Member

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

Copy link
Member

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()

Copy link
Contributor Author

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")},
Copy link
Member

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.

Copy link
Contributor

@loic-sharma loic-sharma Jan 5, 2018

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice read, good point.

Copy link
Contributor Author

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}/") }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why @?

Copy link
Contributor Author

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,
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

@joelverhagen joelverhagen left a 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)}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we lose port?

Copy link
Member

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the port.

@chenriksson
Copy link
Member

@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...

class Routes {
    routes.MapRoute(
        name: RouteName.Profile,
        url: "profiles/{username}",
        defaults: new { controller = "Users", action = "Profiles" },
        obfuscations: new { username = 'ObfuscatedUsername' }
}

static class NuGetRouteExtensions {

    public static Dictionary<string, Dictionary<string, string>> UrlObfuscations;

    // using Dictionary, or could use object ViewBag to be consistent with MVC routing APIs
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, Dictionary<string, string> obfuscations) {
        if (obfuscations != null) {
            Obfuscator.UrlObfuscations[url] = obfuscations;
        }
    }

    public static string ObfuscateUri(this Route route, string actualUri) {
        if (UrlObfuscations.ContainsKey(route.Uri) {
            var obfuscations = UrlObfuscations[route.Uri];
            var parts = routeUri.Split('/');
            var actualParts = actualUri.Split('/');
            for (int i = 0; i < parts.length; i++) {
                if (obfuscations.ContainsKey(parts[i])) {
                    actualParts[i] = obfuscations[parts[i]];
                }
            }
            return actualParts.Join('/');
        }
    }
}

{
return value;
}
string obfuscatedValue = Regex.Replace(value,
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Member

@chenriksson chenriksson Jan 9, 2018

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?

Copy link
Contributor Author

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
Copy link
Member

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>();
Copy link
Member

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?

Copy link
Contributor Author

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; });
Copy link
Member

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?

Copy link
Contributor Author

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.

@@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still necessary?

Copy link
Contributor Author

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;
Copy link
Member

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?

Copy link
Contributor Author

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can revert?

Copy link
Contributor Author

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));
Copy link
Member

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?

Copy link
Contributor Author

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.

@chenriksson
Copy link
Member

@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); }
Copy link
Contributor

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

Copy link
Contributor

@skofman1 skofman1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

return;
}
// Removes the first /
var requestPath = requestTelemetryItem.Url.AbsolutePath.Remove(0,1);
Copy link
Contributor

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?

Copy link
Member

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('/')?

Copy link
Contributor Author

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)
Copy link
Contributor

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicer - updated

@cristinamanum cristinamanum merged commit 18bc2a1 into dev Jan 10, 2018
@cristinamanum cristinamanum deleted the cmanu1043 branch January 11, 2018 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants